From patchwork Thu Mar 21 18:29:28 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Berger X-Patchwork-Id: 229810 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 135192C0089 for ; Fri, 22 Mar 2013 05:51:44 +1100 (EST) Received: from localhost ([::1]:37127 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UIkaU-0002aw-Bi for incoming@patchwork.ozlabs.org; Thu, 21 Mar 2013 14:51:42 -0400 Received: from eggs.gnu.org ([208.118.235.92]:46527) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UIka2-0002a6-Np for qemu-devel@nongnu.org; Thu, 21 Mar 2013 14:51:21 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UIkU6-00086L-6H for qemu-devel@nongnu.org; Thu, 21 Mar 2013 14:45:23 -0400 Received: from e8.ny.us.ibm.com ([32.97.182.138]:37951) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UIkFv-0002MU-N5 for qemu-devel@nongnu.org; Thu, 21 Mar 2013 14:30:27 -0400 Received: from /spool/local by e8.ny.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Thu, 21 Mar 2013 14:30:27 -0400 Received: from d01dlp02.pok.ibm.com (9.56.250.167) by e8.ny.us.ibm.com (192.168.1.108) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Thu, 21 Mar 2013 14:30:25 -0400 Received: from d01relay05.pok.ibm.com (d01relay05.pok.ibm.com [9.56.227.237]) by d01dlp02.pok.ibm.com (Postfix) with ESMTP id 7940F6E804F for ; Thu, 21 Mar 2013 14:30:16 -0400 (EDT) Received: from d03av06.boulder.ibm.com (d03av06.boulder.ibm.com [9.17.195.245]) by d01relay05.pok.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id r2LIUHZj300216 for ; Thu, 21 Mar 2013 14:30:18 -0400 Received: from d03av06.boulder.ibm.com (loopback [127.0.0.1]) by d03av06.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id r2LIWXj4027196 for ; Thu, 21 Mar 2013 12:32:33 -0600 Received: from k-d941f-5.watson.ibm.com (k-d941f-5.watson.ibm.com [9.2.141.165]) by d03av06.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id r2LIWVOV027065 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Thu, 21 Mar 2013 12:32:32 -0600 Received: from k-d941f-5.watson.ibm.com (localhost.localdomain [127.0.0.1]) by k-d941f-5.watson.ibm.com (8.14.5/8.14.3) with ESMTP id r2LIToNA015221; Thu, 21 Mar 2013 14:29:51 -0400 Received: (from root@localhost) by k-d941f-5.watson.ibm.com (8.14.5/8.14.5/Submit) id r2LITn8j015220; Thu, 21 Mar 2013 14:29:49 -0400 From: Stefan Berger To: stefanb@linux.vnet.ibm.com, qemu-devel@nongnu.org, anthony@codemonkey.ws Date: Thu, 21 Mar 2013 14:29:28 -0400 Message-Id: <1363890571-15146-7-git-send-email-stefanb@linux.vnet.ibm.com> X-Mailer: git-send-email 1.7.11.7 In-Reply-To: <1363890571-15146-1-git-send-email-stefanb@linux.vnet.ibm.com> References: <1363890571-15146-1-git-send-email-stefanb@linux.vnet.ibm.com> X-TM-AS-MML: No X-Content-Scanned: Fidelis XPS MAILER x-cbid: 13032118-9360-0000-0000-0000116C9FBC X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic] X-Received-From: 32.97.182.138 Cc: jschopp@linux.vnet.ibm.com, coreyb@linux.vnet.ibm.com, mdroth@linux.vnet.ibm.com, mst@redhat.com Subject: [Qemu-devel] [PATCH v4 6/9] ASN.1 input visitor X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Implement an input visitor for ASN.1 BER and CER encoding. Cc: Michael Tsirkin Signed-off-by: Stefan Berger Signed-off-by: Joel Schopp --- include/qapi/ber-input-visitor.h | 30 + qapi/Makefile.objs | 2 +- qapi/ber-input-visitor.c | 1141 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1172 insertions(+), 1 deletion(-) create mode 100644 include/qapi/ber-input-visitor.h create mode 100644 qapi/ber-input-visitor.c diff --git a/include/qapi/ber-input-visitor.h b/include/qapi/ber-input-visitor.h new file mode 100644 index 0000000..eaa3d0e --- /dev/null +++ b/include/qapi/ber-input-visitor.h @@ -0,0 +1,30 @@ +/* + * BER Input Visitor header + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori + * Stefan Berger + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef BER_INPUT_VISITOR_H +#define BER_INPUT_VISITOR_H + +#include "qapi/visitor.h" + +typedef struct BERInputVisitor BERInputVisitor; + +BERInputVisitor *ber_input_visitor_new(QEMUFile *, + uint64_t max_allowd_buffer_size); +void ber_input_visitor_cleanup(BERInputVisitor *v); +uint64_t ber_input_get_parser_position(BERInputVisitor *v); +uint64_t ber_input_get_largest_needed_buffer(BERInputVisitor *v); + +Visitor *ber_input_get_visitor(BERInputVisitor *v); + +#endif diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs index 519e3ee..f7f080a 100644 --- a/qapi/Makefile.objs +++ b/qapi/Makefile.objs @@ -3,4 +3,4 @@ util-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o util-obj-y += string-input-visitor.o string-output-visitor.o util-obj-y += opts-visitor.o -util-obj-y += ber-common.o ber-output-visitor.o +util-obj-y += ber-common.o ber-output-visitor.o ber-input-visitor.o diff --git a/qapi/ber-input-visitor.c b/qapi/ber-input-visitor.c new file mode 100644 index 0000000..bfc32aa --- /dev/null +++ b/qapi/ber-input-visitor.c @@ -0,0 +1,1141 @@ +/* + * BER Input Visitor + * + * Copyright IBM, Corp. 2011, 2013 + * Copyright Red Hat, Inc. 2011 + * + * Authors: + * Anthony Liguori + * Stefan Berger + * Michael Tsirkin + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include +#include + +#include "qemu-common.h" +#include "qapi/ber-common.h" +#include "qapi/ber-input-visitor.h" +#include "qemu/queue.h" +#include "qemu-common.h" +#include "hw/hw.h" +#include "qapi/ber.h" +#include "include/qapi/qmp/qerror.h" +#include "migration/qemu-file.h" +#include "qapi/visitor-impl.h" + +#define AIV_STACK_SIZE 1024 + +/* whether to allow the parsing of primitives that are fragmented */ +#define BER_ALLOW_FRAGMENTED_PRIMITIVES + +/* #define BER_DEBUG */ + +typedef struct StackEntry { + uint64_t cur_pos; +} StackEntry; + +struct BERInputVisitor { + Visitor visitor; + QEMUFile *qfile; + uint64_t cur_pos; + StackEntry stack[AIV_STACK_SIZE]; + int nb_stack; + uint64_t max_allowed_buffer_size; + uint64_t largest_needed_buffer; +}; + +static BERInputVisitor *to_biv(Visitor *v) +{ + return container_of(v, BERInputVisitor, visitor); +} + +static void ber_input_push(BERInputVisitor *aiv, + uint64_t cur_pos, Error **errp) +{ + aiv->stack[aiv->nb_stack].cur_pos = cur_pos; + aiv->nb_stack++; + + if (aiv->nb_stack >= AIV_STACK_SIZE) { + error_set(errp, QERR_BUFFER_OVERRUN); + } +} + +static uint64_t ber_input_pop(BERInputVisitor *aiv, Error **errp) +{ + aiv->nb_stack--; + + if (aiv->nb_stack < 0) { + error_set(errp, QERR_BUFFER_OVERRUN); + return 0; + } + + return aiv->stack[aiv->nb_stack].cur_pos; +} + +/* + * Read a type tag from the stream. Up-to 32 bit type tags are supported + * for reading and otherwise an error is returned. Anything larger than that + * would not be reasonable and could only be abused. + */ +static uint32_t ber_read_type(BERInputVisitor *aiv, uint8_t *ber_type_flags, + Error **errp) +{ + uint32_t type; + uint8_t byte; + uint8_t ctr = 0; + char buf[128]; + + if (qemu_read_bytes(aiv->qfile, &byte, 1) != 1) { + error_setg(errp, "QEMUFile has an error, error was '%s'", + "Error while reading type"); + return 0; + } + aiv->cur_pos++; + type = byte; + + *ber_type_flags = type & (BER_TYPE_P_C_MASK | BER_TYPE_CLASS_MASK); + + if ((type & BER_TYPE_TAG_MASK) == BER_TYPE_LONG_FORM) { + type = 0; + while (true) { + type <<= 7; + if (qemu_read_bytes(aiv->qfile, &byte, 1) != 1) { + error_setg(errp, "QEMUFile has an error, error was '%s'", + "Error while reading long type"); + return 0; + } + aiv->cur_pos++; + + type |= (byte & 0x7f); + if ((byte & 0x80) == 0) { + break; + } + ctr += 7; /* read 7 bits */ + if (ctr >= (sizeof(type) * 8)) { + /* only support 32 bit length identifiers */ + snprintf(buf, sizeof(buf), + "type tag is larger than 32 bit (offset %" PRIu64 + ")", aiv->cur_pos); + error_setg(errp, "Data stream is invalid, error was '%s'", buf); + return 0; + } + } + } else { + type &= BER_TYPE_TAG_MASK; + } + + return type; +} + +static uint64_t ber_read_length(BERInputVisitor *aiv, bool *is_indefinite, + Error **errp) +{ + uint8_t byte, c, int_len; + uint64_t len = 0; + QEMUFile *qfile = aiv->qfile; + unsigned char int_array[sizeof(len)]; + char buf[128]; + + *is_indefinite = false; + + if (qemu_read_bytes(qfile, &byte, 1) != 1) { + error_setg(errp, "QEMUFile has an error, error was '%s'", + "Error while reading length indicator"); + return ~0x0ULL; + } + aiv->cur_pos++; + + if (byte == BER_LENGTH_INDEFINITE) { + *is_indefinite = true; + return ~0x0ULL; + } + + if (!(byte & BER_LENGTH_LONG)) { + len = byte; + } else { + int_len = byte & BER_LENGTH_MASK; + if (int_len > sizeof(len)) { + snprintf(buf, sizeof(buf), + "ASN.1 integer length field %d > %" PRIu64, + int_len, sizeof(len)); + /* Length can be up to 127 byte, but it seems + * safe to assume any input will be < 1TB in length. */ + error_set(errp, QERR_INVALID_PARAMETER, buf); + return ~0x0ULL; + } + if (qemu_read_bytes(qfile, int_array, int_len) != int_len) { + error_setg(errp, "QEMUFile error: Error while reading length"); + return ~0x0ULL; + } + for (c = 0; c < int_len; c++) { + len <<= 8; + len |= int_array[c]; + } + aiv->cur_pos += int_len; + } + + if (len > aiv->max_allowed_buffer_size) { + snprintf(buf, sizeof(buf), + "Length indicator (%"PRIu64") in input byte stream " + "exceeds maximum allowed length (%"PRIu64").", + len, aiv->max_allowed_buffer_size); + error_setg(errp, "Data stream is invalid, error was '%s'", buf); + return ~0x0ULL; + } + + if (len > aiv->largest_needed_buffer) { + aiv->largest_needed_buffer = len; + } + + return len; +} + +static uint64_t ber_peek_is_eoc(BERInputVisitor *biv, Error **errp) +{ + uint8_t buf[2]; + QEMUFile *qfile = biv->qfile; + + if (qemu_peek_bytes(qfile, buf, 2, 0) != 2) { + error_setg(errp, "QEMUFile has an error, error was '%s'", + "Error while peeking for EOC"); + return ~0x0ULL; + } + + if (buf[0] == BER_TYPE_EOC && buf[1] == 0) { + return 1; + } + + return 0; +} + +static void ber_skip_bytes(BERInputVisitor *aiv, uint64_t to_skip, + Error **errp) +{ + uint8_t buf[1024]; + uint32_t skip; + + /* skip length bytes */ + while (to_skip > 0) { + skip = MIN(to_skip, sizeof(buf)); + if (qemu_read_bytes(aiv->qfile, buf, skip) != skip) { + error_setg(errp, "QEMUFile error: Error while skipping over bytes"); + return; + } + aiv->cur_pos += skip; + to_skip -= skip; + } +} + +static void ber_skip_until_eoc(BERInputVisitor *aiv, Error **errp) +{ + uint32_t ber_type_tag; + uint64_t length; + bool is_indefinite; + uint8_t ber_type_flags; + uint64_t indefinite_nesting = 1; + char buf[128]; + + while (!error_is_set(errp)) { + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + return; + } + + length = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + return; + } + if (ber_type_tag == BER_TYPE_EOC) { + if (length) { + snprintf(buf, sizeof(buf), + "ASN.1 EOC length field at offset %" PRIu64 + " is invalid", aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + return; + } + if (!indefinite_nesting) { + snprintf(buf, sizeof(buf), + "ASN.1 EOC at offset %" PRIu64 + "not within an indefinite length", + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + return; + } +#ifdef BER_DEBUG + fprintf(stderr, "found end! nesting=%" PRIdMAX + ", pos=%" PRIu64 "\n", + indefinite_nesting, aiv->cur_pos); +#endif + if (!--indefinite_nesting) { + return; + } + } + if (is_indefinite) { + if ((ber_type_flags & BER_TYPE_P_C_MASK) == BER_TYPE_PRIMITIVE) { + snprintf(buf, sizeof(buf), + "ASN.1 indefinite length in a primitive type " + "at offset %" PRIu64, + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + return; + } + if (indefinite_nesting == ~0x0ULL) { + snprintf(buf, sizeof(buf), + "ASN.1 indefinite nesting level is too large " + "(offset %" PRIu64 ")", + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + return; + } + ++indefinite_nesting; + } else { +#ifdef BER_DEBUG + fprintf(stderr, "skipping type '%s' of length " + "%" PRIu64 " at %" PRIu64 ".\n", + ber_type_to_str(ber_type_tag), length, aiv->cur_pos); +#endif + ber_skip_bytes(aiv, length, errp); + } + } +} + +static void ber_input_start_constructed(Visitor *v, uint32_t exp_ber_type, + uint8_t exp_ber_flags, void **obj, + const char *kind, const char *name, + size_t size, Error **errp) +{ + BERInputVisitor *aiv = to_biv(v); + uint32_t ber_type_tag; + uint8_t ber_type_flags; + int64_t len; + bool is_indefinite; + char buf[128]; + + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + return; + } + + if (ber_type_tag != exp_ber_type || ber_type_flags != exp_ber_flags) { + sprintf(buf, "%s at offset %" PRIu64, + ber_type_to_str(exp_ber_type), aiv->cur_pos); + + error_set(errp, QERR_INVALID_PARAMETER_TYPE, + ber_type_to_str(ber_type_tag), + buf); + return; + } + + if ((ber_type_flags & BER_TYPE_P_C_MASK) == BER_TYPE_PRIMITIVE) { + snprintf(buf, sizeof(buf), "primitive type (%s)", + ber_type_to_str(ber_type_tag)); + error_set(errp, QERR_INVALID_PARAMETER_TYPE, + buf, "constructed type"); + return; + } + + len = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + return; + } + + if (!is_indefinite) { +#ifdef BER_DEBUG + fprintf(stderr, "structure/set len: %" PRIi64 "\n", len); +#endif + ber_input_push(aiv, aiv->cur_pos + len, errp); + } else { +#ifdef BER_DEBUG + fprintf(stderr, "indefinite length encoding!\n"); +#endif + ber_input_push(aiv, 0, errp); + } + + if (error_is_set(errp)) { + return; + } + + if (size > 0 && *obj == NULL) { + *obj = g_malloc0(size); +#ifdef BER_DEBUG + fprintf(stderr, "for type '%s' allocated buffer at %p, size = %zu\n", + ber_type_to_str(ber_type_tag), *obj, size); +#endif + } +} + +static void ber_input_end_constructed(Visitor *v, Error **errp) +{ + uint64_t new_pos; + BERInputVisitor *aiv = to_biv(v); + + new_pos = ber_input_pop(aiv, errp); + + if (new_pos != 0) { +#ifdef BER_DEBUG + fprintf(stderr, "new_pos = %" PRIu64 "\n", new_pos); +#endif + aiv->cur_pos = new_pos; + } else { +#ifdef BER_DEBUG + fprintf(stderr, "searching for end...\n"); + fprintf(stderr, "cur_pos = %" PRIu64 "\n", aiv->cur_pos); +#endif + ber_skip_until_eoc(aiv, errp); + } +} + +static void ber_input_start_struct(Visitor *v, void **obj, const char *kind, + const char *name, size_t size, Error **errp) +{ + ber_input_start_constructed(v, BER_TYPE_SEQUENCE, BER_TYPE_CONSTRUCTED, + obj, kind, name, size, errp); +} + +static void ber_input_end_struct(Visitor *v, Error **errp) +{ + ber_input_end_constructed(v, errp); +} + +static void ber_input_start_list(Visitor *v, const char *name, + Error **errp) +{ + void *obj = NULL; + ber_input_start_constructed(v, BER_TYPE_CUSTOM_LIST, BER_TYPE_CONSTRUCTED, + obj, NULL, name, 0, errp); + g_free(obj); +} + +static GenericList *ber_input_next_list(Visitor *v, GenericList **list, + Error **errp) +{ + BERInputVisitor *biv = to_biv(v); + GenericList *entry; + StackEntry *se = &biv->stack[biv->nb_stack - 1]; + + if (se->cur_pos == 0) { + /* indefinite lenght encoding is used */ + se = &biv->stack[biv->nb_stack]; + if (ber_peek_is_eoc(biv, errp) != 0) { + return NULL; + } + } else if (se->cur_pos <= biv->cur_pos) { + return NULL; + } + + entry = g_malloc0(sizeof(*entry)); + if (*list) { + (*list)->next = entry; + } + + return entry; +} + +static void ber_input_end_list(Visitor *v, Error **errp) +{ + ber_input_end_constructed(v, errp); +} + +static void ber_input_integer(Visitor *v, uint8_t *obj, uint8_t maxbytes, + Error **errp) +{ + BERInputVisitor *aiv = to_biv(v); + uint32_t ber_type_tag; + uint8_t ber_type_flags; + bool is_indefinite; + uint64_t len; + uint64_t val = 0; + unsigned char int_array[sizeof(val)]; + int c; + char buf[128], buf2[128]; + +#ifdef BER_DEBUG + fprintf(stderr, "reading int to %p\n", obj); +#endif + + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + return; + } + +#ifdef BER_DEBUG + fprintf(stderr, "%s: got type: 0x%02x, expected 0x%02x\n", + __func__, ber_type_tag, BER_TYPE_INTEGER); +#endif + + if (ber_type_tag != BER_TYPE_INTEGER || + ber_type_flags != (BER_TYPE_CLASS_APPLICATION|BER_TYPE_PRIMITIVE)) { + snprintf(buf, sizeof(buf), "%s/%s/%s", + ber_type_class_to_str(ber_type_flags), + ber_type_pc_to_str(ber_type_flags), + ber_type_to_str(ber_type_tag)); + snprintf(buf2, sizeof(buf2), "%s/%s/%s", + ber_type_class_to_str(BER_TYPE_CLASS_APPLICATION), + ber_type_pc_to_str(BER_TYPE_PRIMITIVE), + ber_type_to_str(BER_TYPE_INTEGER)); + error_set(errp, QERR_INVALID_PARAMETER_TYPE, buf, buf2); + return; + } + + len = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + return; + } +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " int len: %" PRIi64 "\n", + aiv->cur_pos, len); +#endif + + if (is_indefinite) { + error_set(errp, QERR_INVALID_PARAMETER_VALUE, + "ASN.1 int indicator is indefinite", + "[1..8]"); + return; + } + + if (maxbytes > sizeof(val)) { + snprintf(buf, sizeof(buf), "ASN.1 integers cannot have a length of " + "%" PRIi32 " bytes", maxbytes); + error_set(errp, QERR_INVALID_PARAMETER_VALUE, + buf, "[1..8]"); + return; + } + + if (len > maxbytes) { + snprintf(buf, sizeof(buf), "ASN.1 integer length indicator %" PRIi64 + " is larger than expected (%u bytes)", + len, maxbytes); + error_set(errp, QERR_INVALID_PARAMETER_VALUE, + buf, "[1..8]"); + return; + } + + if (qemu_read_bytes(aiv->qfile, int_array, len) != len) { + error_setg(errp, "QEMUFile error: Error while reading integer"); + return; + } + + for (c = 0; c < len ; c++) { + val <<= 8; + val |= int_array[c]; + if (c == 0 && (val & 0x80) == 0x80) { + /* sign extend */ + val |= 0xFFFFFFFFFFFFFF00ULL; + } + } + aiv->cur_pos += len; +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " int: %" PRIu64 "\n", aiv->cur_pos, val); +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN + memcpy(obj, &val, maxbytes); +#elif __BYTE_ORDER == __BIG_ENDIAN + memcpy(obj, &((char *)&val)[8-maxbytes], maxbytes); +#endif +} + +static void ber_input_type_int(Visitor *v, int64_t *obj, const char *name, + Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_uint8(Visitor *v, uint8_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_uint16(Visitor *v, uint16_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_uint32(Visitor *v, uint32_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_uint64(Visitor *v, uint64_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_int8(Visitor *v, int8_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_int16(Visitor *v, int16_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_int32(Visitor *v, int32_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_int64(Visitor *v, int64_t *obj, + const char *name, Error **errp) +{ + ber_input_integer(v, (uint8_t *)obj, sizeof(*obj), errp); +} + +static void ber_input_type_bool(Visitor *v, bool *obj, const char *name, + Error **errp) +{ + BERInputVisitor *aiv = to_biv(v); + uint32_t ber_type_tag; + uint8_t ber_type_flags, byte; + bool is_indefinite; + uint64_t len; + char buf[128]; + + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + return; + } + + if (ber_type_tag != BER_TYPE_BOOLEAN || ber_type_flags != 0) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, + ber_type_to_str(ber_type_tag), + ber_type_to_str(BER_TYPE_BOOLEAN)); + return; + } + len = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + return; + } +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " bool len: %" PRIi64 "\n", + aiv->cur_pos, len); +#endif + + if (is_indefinite || len != 1) { + snprintf(buf, sizeof(buf), + "ASN.1 bool length indicator at offset %" PRIu64 + " is indefinite or != 1", + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER_VALUE, + buf, "1"); + return; + } + if (qemu_read_bytes(aiv->qfile, &byte, 1) != 1) { + error_setg(errp, "QEMUFile error: Error while reading boolean"); + return; + } + aiv->cur_pos++; + *obj = byte; + +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " bool: %d\n", aiv->cur_pos, *obj); +#endif +} + +/* Function for recursive reading of fragmented primitives */ +static uint32_t ber_input_fragment(BERInputVisitor *aiv, + uint32_t exp_type_tag, + uint8_t exp_type_flags, + uint8_t **buffer, + size_t *buffer_len, size_t elem_size, + bool may_realloc, + uint32_t offset, uint32_t nesting, + bool indefinite, uint64_t max_pos, + const char *name, Error **errp) +{ + uint32_t ber_type_tag; + uint8_t ber_type_flags; + uint32_t bytes_read = 0; + bool is_indefinite; + uint64_t len; + char buf[128]; + uint8_t *conv_buffer; + size_t num_elms; +#if __BYTE_ORDER == __LITTLE_ENDIAN + size_t i; + uint16_t *d16, *s16; + uint32_t *d32, *s32; + uint64_t *d64, *s64; +#endif + bool is_bigendian; + +#if __BYTE_ORDER == __BIG_ENDIAN + is_bigendian = true; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + is_bigendian = false; +#else +# error Unsupported endianess +#endif + + assert((exp_type_flags & BER_TYPE_CONSTRUCTED) == BER_TYPE_PRIMITIVE); + + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + return 0; + } + + if (ber_type_tag != exp_type_tag) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, + ber_type_to_str(ber_type_tag & BER_TYPE_TAG_MASK), + ber_type_to_str(exp_type_tag)); + return 0; + } + + if ((ber_type_flags & BER_TYPE_CONSTRUCTED)) { +#ifndef BER_ALLOW_FRAGMENTED_PRIMITIVES + error_setg(errp, "Data stream is invalid, error was '%s'", + "constructed encoding of primitive types is not supported"); + goto err_exit; +#else + if (nesting == 1) { + /* don't allow further nesting */ + error_setg(errp, "Data stream is invalid, error was invalid " + "nesting"); + goto err_exit; + } + len = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + goto err_exit; + } +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " string len: %" PRIi64 "\n", + aiv->cur_pos, len); +#endif + + if (!is_indefinite) { + if ((*buffer) == NULL) { + /* allocate buffer once; due to the ASN.1 overhead it + * will be bigger than what we need */ + *buffer = g_malloc0(len); + *buffer_len = len; + may_realloc = false; + } + } +#ifdef BER_DEBUG + fprintf(stderr, "recursing now to read constructed type.\n"); + fprintf(stderr, " is_indefinite: %d\n", is_indefinite); +#endif + bytes_read += ber_input_fragment(aiv, exp_type_tag, exp_type_flags, + buffer, buffer_len, elem_size, + may_realloc, offset, nesting + 1, + is_indefinite, aiv->cur_pos + len, + name, errp); + return bytes_read; +#endif + } + + while (true) { + /* Would reading the length carry us beyond what we are allowed to + * read? + */ + if (!indefinite && + max_pos != 0 && + aiv->cur_pos + 1 > max_pos) { + snprintf(buf, sizeof(buf), + "data stream would cause parsing beyond " + "allowed offset at %" PRIu64, + max_pos); + /* input stream is malformed */ + error_setg(errp, "Data stream is invalid, error was '%s'", buf); + goto err_exit; + } + + /* not-constructed case */ + len = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + goto err_exit; + } +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " string len: %" PRIi64 "\n", + aiv->cur_pos, len); +#endif + if (is_indefinite) { + snprintf(buf, sizeof(buf), + "Got indefinite type length in primitive type (%s) at" + "offset %" PRIu64, + ber_type_to_str(ber_type_tag), aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + goto err_exit; + } + /* if max_pos is not set, set it here */ + if (!indefinite && max_pos == 0) { + max_pos = aiv->cur_pos + len; + } + + /* Would reading the data carry us beyond what we are allowed to + * read ? + */ + if (!indefinite && aiv->cur_pos + len > max_pos) { + /* input stream is malformed */ + snprintf(buf, sizeof(buf), + "data stream would cause parsing beyond " + "allowed offset at %" PRIu64, + max_pos); + error_setg(errp, "Data stream is invalid, error was '%s'", buf); + goto err_exit; + } + + if (offset + len > *buffer_len) { + if (!may_realloc) { + snprintf(buf, sizeof(buf), + "given buffer is too small (%lu < %"PRIu64")", + (unsigned long)*buffer_len, offset + len); + error_setg(errp, "Data stream is invalid, error was '%s'", buf); + } + /* allocate one more byte for strings, set to 0 */ + *buffer = g_realloc(*buffer, offset + len + 1); + *buffer_len = offset + len; + (*buffer)[offset+len] = 0; + } + + num_elms = len / elem_size; + if (num_elms * elem_size != len) { + error_setg(errp, "Stream contains broken up element of size " + "%"PRIi64, elem_size); + goto err_exit; + } + + if (elem_size != sizeof(uint8_t) && !is_bigendian) { + /* intermediate buffer for endianess conversion */ + conv_buffer = g_malloc(len); + } else { + conv_buffer = &((uint8_t *)*buffer)[offset]; + } + + if (qemu_read_bytes(aiv->qfile, conv_buffer, len) != len) { + error_setg(errp, "QEMUFile error: Error while reading data"); + goto err_exit; + } + + switch (elem_size) { + case sizeof(uint8_t): +#if __BYTE_ORDER == __BIG_ENDIAN + case sizeof(uint16_t): + case sizeof(uint32_t): + case sizeof(uint64_t): +#endif + /* nothing to convert */ + break; +#if __BYTE_ORDER == __LITTLE_ENDIAN + case sizeof(uint16_t): + s16 = (uint16_t *)conv_buffer; + d16 = (uint16_t *)&((uint8_t *)*buffer)[offset]; + for (i = 0; i < num_elms; i++) { + d16[i] = be16_to_cpu(s16[i]); + } + break; + case sizeof(uint32_t): + s32 = (uint32_t *)conv_buffer; + d32 = (uint32_t *)&((uint8_t *)*buffer)[offset]; + for (i = 0; i < num_elms; i++) { + d32[i] = be32_to_cpu(s32[i]); + } + break; + case sizeof(uint64_t): + s64 = (uint64_t *)conv_buffer; + d64 = (uint64_t *)&((uint8_t *)*buffer)[offset]; + for (i = 0; i < num_elms; i++) { + d64[i] = be64_to_cpu(s64[i]); + } + break; +#endif + } + + if (elem_size != sizeof(uint8_t) && !is_bigendian) { + /* intermediate buffer for endianess conversion */ + g_free(conv_buffer); + } + + offset += len; + bytes_read += len; + + aiv->cur_pos += len; +#ifdef BER_DEBUG + if (exp_type_tag == BER_TYPE_IA5_STRING) { + fprintf(stderr, "pos: %" PRIu64 " string: %.*s\n", aiv->cur_pos, + offset, *buffer); + } +#endif + + if (nesting == 0) { + break; + } + + /* indefinite length case: loop until we encounter EOC */ + if (indefinite) { + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + goto err_exit; + } + + if (ber_type_tag == BER_TYPE_EOC) { + uint8_t byte; + if (qemu_read_bytes(aiv->qfile, &byte, 1) != 1) { + error_setg(errp, "QEMUFile error: Error while reading " + "BER_TYPE_EOC length"); + goto err_exit; + } + aiv->cur_pos++; + + if (byte != 0) { + snprintf(buf, sizeof(buf), + "ASN.1 EOC length field is invalid at offset " + "%" PRIu64, + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + goto err_exit; + } + return bytes_read; + } + + if (ber_type_tag != exp_type_tag || + ber_type_flags != exp_type_flags) { + snprintf(buf, sizeof(buf), + "ASN.1 type field or flags are wrong. Found " + "0x%x/%u, expected " + "0x%x/%u at offset %" PRIu64, + ber_type_tag, ber_type_flags, + exp_type_tag, exp_type_flags, + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + goto err_exit; + } + continue; + } + + /* in definite length coding case; caller told us how far to read */ + if (aiv->cur_pos == max_pos) { + return bytes_read; + } + + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + goto err_exit; + } + + if ((ber_type_flags & BER_TYPE_P_C_MASK) == BER_TYPE_CONSTRUCTED) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, + "constructed BER type", + ber_type_to_str(exp_type_tag)); + goto err_exit; + } + + if (ber_type_tag != exp_type_tag) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, + ber_type_to_str(ber_type_tag & BER_TYPE_TAG_MASK), + ber_type_to_str(exp_type_tag)); + goto err_exit; + } + } + return bytes_read; + +err_exit: + if (may_realloc) { + g_free(*buffer); + *buffer = NULL; + } + return 0; +} + +static void ber_input_type_str(Visitor *v, char **obj, const char *name, + Error **errp) +{ + BERInputVisitor *aiv = to_biv(v); + size_t buffer_len = 0; + + ber_input_fragment(aiv, BER_TYPE_IA5_STRING, 0, + (uint8_t **)obj, &buffer_len, 1, (*obj == NULL), + 0, 0, false, 0, name, errp); + + if (!error_is_set(errp) && *obj == NULL) { + /* adjust NULL string to "" */ + *obj = g_strdup(""); + } +} + +static void ber_input_sized_buffer(Visitor *v, void **obj, const char *name, + size_t num_elem, size_t elem_size, + Error **errp) +{ + BERInputVisitor *aiv = to_biv(v); + size_t len = num_elem * elem_size; + + if (*obj == NULL) { + /* + * Allocating the memory will prevent a smaller structure from + * being allocated by ber_input_fragment. + * Setting len = 0 would allow less bytes to be allocated than + * expected by the caller, which may cause segfaults if the caller + * then steps on unallocated memory. + */ + *obj = g_malloc(len); + } + + ber_input_fragment(aiv, BER_TYPE_OCTET_STRING, 0, + (uint8_t **)obj, &len, elem_size, (*obj == NULL), + 0, 0, false, 0, name, errp); + +#ifdef BER_DEBUG + fprintf(stderr, "pos: %" PRIu64 " data at: %p data:\n", + aiv->cur_pos, *obj); + int i; + for (i = 0; i < len; i++) { + fprintf(stderr, "%02x ", (*obj)[i]); + if ((i & 0xf) == 0xf) { + fprintf(stderr, "\n"); + } + } + fprintf(stderr, "\n"); +#endif +} + +static void ber_input_type_number(Visitor *v, double *obj, const char *name, + Error **errp) +{ + BERInputVisitor *aiv = to_biv(v); + uint32_t ber_type_tag; + uint8_t ber_type_flags; + uint32_t len; + bool is_indefinite; + char buf[128], buf2[128]; + GDoubleIEEE754 num; + struct ieee754_buffer number; + size_t to_read; + + ber_type_tag = ber_read_type(aiv, &ber_type_flags, errp); + if (error_is_set(errp)) { + return; + } + + if (ber_type_tag != BER_TYPE_REAL || + ber_type_flags != (BER_TYPE_CLASS_APPLICATION|BER_TYPE_PRIMITIVE)) { + snprintf(buf, sizeof(buf), "%s/%s/%s", + ber_type_class_to_str(ber_type_flags), + ber_type_pc_to_str(ber_type_flags), + ber_type_to_str(ber_type_tag)); + snprintf(buf2, sizeof(buf2), "%s/%s/%s", + ber_type_class_to_str(BER_TYPE_CLASS_APPLICATION), + ber_type_pc_to_str(BER_TYPE_PRIMITIVE), + ber_type_to_str(BER_TYPE_REAL)); + error_set(errp, QERR_INVALID_PARAMETER_TYPE, buf, buf2); + return; + } + + len = ber_read_length(aiv, &is_indefinite, errp); + if (error_is_set(errp)) { + return; + } + + to_read = sizeof(number) - offsetof(struct ieee754_buffer, first); + + if (len != to_read) { + snprintf(buf, sizeof(buf), + "Length indicator in input byte stream " + "of real has unexpected length %"PRIu32"; " + "expected %" PRIu64, + len, sizeof(buf)); + error_set(errp, QERR_INVALID_PARAMETER, buf); + return; + } + + if (is_indefinite) { + snprintf(buf, sizeof(buf), + "ASN.1 indefinite length in a real type " + "at offset %" PRIu64, + aiv->cur_pos); + error_set(errp, QERR_INVALID_PARAMETER, buf); + return; + } + + if (qemu_read_bytes(aiv->qfile, &number.first, to_read) != to_read) { + error_setg(errp, "QEMUFile error: Error while reading real"); + return; + } + + switch (number.first) { + case 0x42: + *obj = nan("NAN"); + break; + case 0x41: + case 0x40: + num.mpn.sign = ((number.first & 0x1) != 0); + num.mpn.biased_exponent = ~0; + num.mpn.mantissa_low = 0; + num.mpn.mantissa_high = 0; + *obj = num.v_double; + break; + default: + num.mpn.sign = ((number.first & 0x40) != 0); + num.mpn.biased_exponent = be16_to_cpu(number.exponent); + num.mpn.mantissa_low = be32_to_cpu(number.mant_lo); + num.mpn.mantissa_high = be32_to_cpu(number.mant_hi); + *obj = num.v_double; + } +} + +Visitor *ber_input_get_visitor(BERInputVisitor *v) +{ + return &v->visitor; +} + +uint64_t ber_input_get_parser_position(BERInputVisitor *v) +{ + return v->cur_pos; +} + +uint64_t ber_input_get_largest_needed_buffer(BERInputVisitor *v) +{ + return v->largest_needed_buffer; +} + +void ber_input_visitor_cleanup(BERInputVisitor *v) +{ + g_free(v); +} + +BERInputVisitor *ber_input_visitor_new(QEMUFile *qfile, + uint64_t max_allowed_buffer_size) +{ + BERInputVisitor *v; + + v = g_malloc0(sizeof(*v)); + + v->visitor.start_struct = ber_input_start_struct; + v->visitor.end_struct = ber_input_end_struct; + v->visitor.start_list = ber_input_start_list; + v->visitor.next_list = ber_input_next_list; + v->visitor.end_list = ber_input_end_list; + v->visitor.type_int = ber_input_type_int; + v->visitor.type_uint8 = ber_input_type_uint8; + v->visitor.type_uint16 = ber_input_type_uint16; + v->visitor.type_uint32 = ber_input_type_uint32; + v->visitor.type_uint64 = ber_input_type_uint64; + v->visitor.type_int8 = ber_input_type_int8; + v->visitor.type_int16 = ber_input_type_int16; + v->visitor.type_int32 = ber_input_type_int32; + v->visitor.type_int64 = ber_input_type_int64; + v->visitor.type_bool = ber_input_type_bool; + v->visitor.type_str = ber_input_type_str; + v->visitor.type_sized_buffer = ber_input_sized_buffer; + v->visitor.type_number = ber_input_type_number; + + v->qfile = qfile; + v->cur_pos = 0; + v->max_allowed_buffer_size = max_allowed_buffer_size; + v->largest_needed_buffer = 0; + + return v; +}