From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([208.118.235.92]:34983) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEeb1-0000hP-FL for qemu-devel@nongnu.org; Mon, 02 Apr 2012 06:34:52 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SEeay-0008Ew-MT for qemu-devel@nongnu.org; Mon, 02 Apr 2012 06:34:47 -0400 Received: from mail-lpp01m010-f45.google.com ([209.85.215.45]:54758) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEeay-00087s-7U for qemu-devel@nongnu.org; Mon, 02 Apr 2012 06:34:44 -0400 Received: by lahe6 with SMTP id e6so3287137lah.4 for ; Mon, 02 Apr 2012 03:34:41 -0700 (PDT) MIME-Version: 1.0 In-Reply-To: <1332417072-20329-9-git-send-email-pbonzini@redhat.com> References: <1332417072-20329-1-git-send-email-pbonzini@redhat.com> <1332417072-20329-9-git-send-email-pbonzini@redhat.com> Date: Mon, 2 Apr 2012 12:34:41 +0200 Message-ID: From: Laurent Desnogues Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Subject: Re: [Qemu-devel] [PATCH 08/10] qapi: add strict mode to input visitor List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Paolo Bonzini Cc: anthony@codemonkey.vs, lcapitulino@redhat.com, eblake@redhat.com, qemu-devel@nongnu.org, mdroth@linux.vnet.ibm.com Hello, On Thu, Mar 22, 2012 at 12:51 PM, Paolo Bonzini wrote= : > While QMP in general is designed so that it is possible to ignore > unknown arguments, in the case of the QMP server it is better to > reject them to detect bad clients. =A0In fact, we're already doing > this at the top level in the argument checker. =A0To extend this to > complex structures, add a mode to the input visitor where it checks > for unvisited keys and raises an error if it finds one. > > Signed-off-by: Paolo Bonzini > --- > =A0qapi/qmp-input-visitor.c | =A0 48 +++++++++- > =A0qapi/qmp-input-visitor.h | =A0 =A02 + > =A0test-qmp-input-strict.c =A0| =A0234 ++++++++++++++++++++++++++++++++++= ++++++++++++ > =A0tests/Makefile =A0 =A0 =A0 =A0 =A0 | =A0 =A05 +- > =A04 files changed, 285 insertions(+), 4 deletions(-) > =A0create mode 100644 test-qmp-input-strict.c > > diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c > index 97a0186..eb09874 100644 > --- a/qapi/qmp-input-visitor.c > +++ b/qapi/qmp-input-visitor.c > @@ -24,6 +24,7 @@ typedef struct StackObject > =A0{ > =A0 =A0 QObject *obj; > =A0 =A0 const QListEntry *entry; > + =A0 =A0GHashTable *h; > =A0} StackObject; > > =A0struct QmpInputVisitor > @@ -31,6 +32,7 @@ struct QmpInputVisitor > =A0 =A0 Visitor visitor; > =A0 =A0 StackObject stack[QIV_STACK_SIZE]; > =A0 =A0 int nb_stack; > + =A0 =A0bool strict; > =A0}; > > =A0static QmpInputVisitor *to_qiv(Visitor *v) > @@ -45,6 +47,9 @@ static QObject *qmp_input_get_object(QmpInputVisitor *q= iv, > > =A0 =A0 if (qobj) { > =A0 =A0 =A0 =A0 if (name && qobject_type(qobj) =3D=3D QTYPE_QDICT) { > + =A0 =A0 =A0 =A0 =A0 =A0if (qiv->stack[qiv->nb_stack - 1].h) { > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0g_hash_table_remove(qiv->stack[qiv->nb_s= tack - 1].h, name); > + =A0 =A0 =A0 =A0 =A0 =A0} > =A0 =A0 =A0 =A0 =A0 =A0 return qdict_get(qobject_to_qdict(qobj), name); > =A0 =A0 =A0 =A0 } else if (qiv->stack[qiv->nb_stack - 1].entry) { > =A0 =A0 =A0 =A0 =A0 =A0 return qlist_entry_obj(qiv->stack[qiv->nb_stack -= 1].entry); > @@ -54,20 +59,47 @@ static QObject *qmp_input_get_object(QmpInputVisitor = *qiv, > =A0 =A0 return qobj; > =A0} > > +static void qdict_add_key(const char *key, QObject *obj, void *opaque) > +{ > + =A0 =A0GHashTable *h =3D opaque; > + =A0 =A0g_hash_table_insert(h, (gpointer) key, NULL); > +} > + > =A0static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error *= *errp) > =A0{ > - =A0 =A0qiv->stack[qiv->nb_stack].obj =3D obj; > - =A0 =A0qiv->stack[qiv->nb_stack].entry =3D NULL; > - =A0 =A0qiv->nb_stack++; > + =A0 =A0GHashTable *h; > > =A0 =A0 if (qiv->nb_stack >=3D QIV_STACK_SIZE) { > =A0 =A0 =A0 =A0 error_set(errp, QERR_BUFFER_OVERRUN); > =A0 =A0 =A0 =A0 return; > =A0 =A0 } > + > + =A0 =A0qiv->stack[qiv->nb_stack].obj =3D obj; > + =A0 =A0qiv->stack[qiv->nb_stack].entry =3D NULL; > + =A0 =A0qiv->stack[qiv->nb_stack].h =3D NULL; > + > + =A0 =A0if (qiv->strict && qobject_type(obj) =3D=3D QTYPE_QDICT) { > + =A0 =A0 =A0 =A0h =3D g_hash_table_new(g_str_hash, g_str_equal); > + =A0 =A0 =A0 =A0qdict_iter(qobject_to_qdict(obj), qdict_add_key, h); > + =A0 =A0 =A0 =A0qiv->stack[qiv->nb_stack].h =3D h; > + =A0 =A0} > + > + =A0 =A0qiv->nb_stack++; > =A0} > > =A0static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp) > =A0{ > + =A0 =A0GHashTableIter iter; GHashTableIter is alas not available in the glib (2.12) that the distros we use at work run. Is there a workaround for this issue? Thanks, Laurent > + =A0 =A0gpointer key; > + > + =A0 =A0if (qiv->strict && qiv->stack[qiv->nb_stack - 1].h) { > + =A0 =A0 =A0 =A0g_hash_table_iter_init(&iter, qiv->stack[qiv->nb_stack -= 1].h); > + =A0 =A0 =A0 =A0if (g_hash_table_iter_next(&iter, &key, NULL)) { > + =A0 =A0 =A0 =A0 =A0 =A0error_set(errp, QERR_QMP_EXTRA_MEMBER, (char *) = key); > + =A0 =A0 =A0 =A0} > + =A0 =A0 =A0 =A0g_hash_table_unref(qiv->stack[qiv->nb_stack - 1].h); > + =A0 =A0} > + > =A0 =A0 assert(qiv->nb_stack > 0); > =A0 =A0 qiv->nb_stack--; > =A0} > @@ -257,3 +289,13 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj) > > =A0 =A0 return v; > =A0} > + > +QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj) > +{ > + =A0 =A0QmpInputVisitor *v; > + > + =A0 =A0v =3D qmp_input_visitor_new(obj); > + =A0 =A0v->strict =3D true; > + > + =A0 =A0return v; > +} > diff --git a/qapi/qmp-input-visitor.h b/qapi/qmp-input-visitor.h > index 3f798f0..e0a48a5 100644 > --- a/qapi/qmp-input-visitor.h > +++ b/qapi/qmp-input-visitor.h > @@ -20,6 +20,8 @@ > =A0typedef struct QmpInputVisitor QmpInputVisitor; > > =A0QmpInputVisitor *qmp_input_visitor_new(QObject *obj); > +QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj); > + > =A0void qmp_input_visitor_cleanup(QmpInputVisitor *v); > > =A0Visitor *qmp_input_get_visitor(QmpInputVisitor *v); > diff --git a/test-qmp-input-strict.c b/test-qmp-input-strict.c > new file mode 100644 > index 0000000..f6df8cb > --- /dev/null > +++ b/test-qmp-input-strict.c > @@ -0,0 +1,234 @@ > +/* > + * QMP Input Visitor unit-tests (strict mode). > + * > + * Copyright (C) 2011-2012 Red Hat Inc. > + * > + * Authors: > + * =A0Luiz Capitulino > + * =A0Paolo Bonzini > + * > + * This work is licensed under the terms of the GNU GPL, version 2 or la= ter. > + * See the COPYING file in the top-level directory. > + */ > + > +#include > +#include > + > +#include "qapi/qmp-input-visitor.h" > +#include "test-qapi-types.h" > +#include "test-qapi-visit.h" > +#include "qemu-objects.h" > + > +typedef struct TestInputVisitorData { > + =A0 =A0QObject *obj; > + =A0 =A0QmpInputVisitor *qiv; > +} TestInputVisitorData; > + > +static void validate_teardown(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 const void = *unused) > +{ > + =A0 =A0qobject_decref(data->obj); > + =A0 =A0data->obj =3D NULL; > + > + =A0 =A0if (data->qiv) { > + =A0 =A0 =A0 =A0qmp_input_visitor_cleanup(data->qiv); > + =A0 =A0 =A0 =A0data->qiv =3D NULL; > + =A0 =A0} > +} > + > +/* This is provided instead of a test setup function so that the JSON > + =A0 string used by the tests are kept in the test functions (and not > + =A0 int main()) */ > +static GCC_FMT_ATTR(2, 3) > +Visitor *validate_test_init(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 const char *jso= n_string, ...) > +{ > + =A0 =A0Visitor *v; > + =A0 =A0va_list ap; > + > + =A0 =A0va_start(ap, json_string); > + =A0 =A0data->obj =3D qobject_from_jsonv(json_string, &ap); > + =A0 =A0va_end(ap); > + > + =A0 =A0g_assert(data->obj !=3D NULL); > + > + =A0 =A0data->qiv =3D qmp_input_visitor_new_strict(data->obj); > + =A0 =A0g_assert(data->qiv !=3D NULL); > + > + =A0 =A0v =3D qmp_input_get_visitor(data->qiv); > + =A0 =A0g_assert(v !=3D NULL); > + > + =A0 =A0return v; > +} > + > +typedef struct TestStruct > +{ > + =A0 =A0int64_t integer; > + =A0 =A0bool boolean; > + =A0 =A0char *string; > +} TestStruct; > + > +static void visit_type_TestStruct(Visitor *v, TestStruct **obj, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0cons= t char *name, Error **errp) > +{ > + =A0 =A0visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(T= estStruct), > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 errp); > + > + =A0 =A0visit_type_int(v, &(*obj)->integer, "integer", errp); > + =A0 =A0visit_type_bool(v, &(*obj)->boolean, "boolean", errp); > + =A0 =A0visit_type_str(v, &(*obj)->string, "string", errp); > + > + =A0 =A0visit_end_struct(v, errp); > +} > + > +static void test_validate_struct(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0cons= t void *unused) > +{ > + =A0 =A0TestStruct *p =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "{ 'integer': -42, 'boolean': tru= e, 'string': 'foo' }"); > + > + =A0 =A0visit_type_TestStruct(v, &p, NULL, &errp); > + =A0 =A0g_assert(!error_is_set(&errp)); > + =A0 =A0g_free(p->string); > + =A0 =A0g_free(p); > +} > + > +static void test_validate_struct_nested(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 const void *unused) > +{ > + =A0 =A0UserDefNested *udp =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "{ 'string0': 'string0', 'dict1':= { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': = 'string' }, 'string2': 'string2'}}}"); > + > + =A0 =A0visit_type_UserDefNested(v, &udp, NULL, &errp); > + =A0 =A0g_assert(!error_is_set(&errp)); > + =A0 =A0qapi_free_UserDefNested(udp); > +} > + > +static void test_validate_list(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0const vo= id *unused) > +{ > + =A0 =A0UserDefOneList *head =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "[ { 'string': 'string0', 'intege= r': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'i= nteger': 44 } ]"); > + > + =A0 =A0visit_type_UserDefOneList(v, &head, NULL, &errp); > + =A0 =A0g_assert(!error_is_set(&errp)); > + =A0 =A0qapi_free_UserDefOneList(head); > +} > + > +static void test_validate_union(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 const v= oid *unused) > +{ > + =A0 =A0UserDefUnion *tmp =3D NULL; > + =A0 =A0Visitor *v; > + =A0 =A0Error *errp =3D NULL; > + > + =A0 =A0v =3D validate_test_init(data, "{ 'type': 'b', 'data' : { 'integ= er': 42 } }"); > + > + =A0 =A0visit_type_UserDefUnion(v, &tmp, NULL, &errp); > + =A0 =A0g_assert(!error_is_set(&errp)); > + =A0 =A0qapi_free_UserDefUnion(tmp); > +} > + > +static void test_validate_fail_struct(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 const void *unused) > +{ > + =A0 =A0TestStruct *p =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "{ 'integer': -42, 'boolean': tru= e, 'string': 'foo', 'extra': 42 }"); > + > + =A0 =A0visit_type_TestStruct(v, &p, NULL, &errp); > + =A0 =A0g_assert(error_is_set(&errp)); > + =A0 =A0if (p) { > + =A0 =A0 =A0 =A0g_free(p->string); > + =A0 =A0} > + =A0 =A0g_free(p); > +} > + > +static void test_validate_fail_struct_nested(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0 =A0 =A0 =A0 =A0const void *unused) > +{ > + =A0 =A0UserDefNested *udp =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "{ 'string0': 'string0', 'dict1':= { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': = 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}"); > + > + =A0 =A0visit_type_UserDefNested(v, &udp, NULL, &errp); > + =A0 =A0g_assert(error_is_set(&errp)); > + =A0 =A0qapi_free_UserDefNested(udp); > +} > + > +static void test_validate_fail_list(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= const void *unused) > +{ > + =A0 =A0UserDefOneList *head =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "[ { 'string': 'string0', 'intege= r': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'i= nteger': 44, 'extra': 'ggg' } ]"); > + > + =A0 =A0visit_type_UserDefOneList(v, &head, NULL, &errp); > + =A0 =A0g_assert(error_is_set(&errp)); > + =A0 =A0qapi_free_UserDefOneList(head); > +} > + > +static void test_validate_fail_union(TestInputVisitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0= =A0const void *unused) > +{ > + =A0 =A0UserDefUnion *tmp =3D NULL; > + =A0 =A0Error *errp =3D NULL; > + =A0 =A0Visitor *v; > + > + =A0 =A0v =3D validate_test_init(data, "{ 'type': 'b', 'data' : { 'integ= er': 42 }, 'extra': 'yyy' }"); > + > + =A0 =A0visit_type_UserDefUnion(v, &tmp, NULL, &errp); > + =A0 =A0g_assert(error_is_set(&errp)); > + =A0 =A0qapi_free_UserDefUnion(tmp); > +} > + > +static void validate_test_add(const char *testpath, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 TestInputVi= sitorData *data, > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 void (*test= _func)(TestInputVisitorData *data, const void *user_data)) > +{ > + =A0 =A0g_test_add(testpath, TestInputVisitorData, data, NULL, test_func= , > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 validate_teardown); > +} > + > +int main(int argc, char **argv) > +{ > + =A0 =A0TestInputVisitorData testdata; > + > + =A0 =A0g_test_init(&argc, &argv, NULL); > + > + =A0 =A0validate_test_add("/visitor/input-strict/pass/struct", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_st= ruct); > + =A0 =A0validate_test_add("/visitor/input-strict/pass/struct-nested", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_st= ruct_nested); > + =A0 =A0validate_test_add("/visitor/input-strict/pass/list", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_li= st); > + =A0 =A0validate_test_add("/visitor/input-strict/pass/union", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_un= ion); > + =A0 =A0validate_test_add("/visitor/input-strict/fail/struct", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_fa= il_struct); > + =A0 =A0validate_test_add("/visitor/input-strict/fail/struct-nested", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_fa= il_struct_nested); > + =A0 =A0validate_test_add("/visitor/input-strict/fail/list", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_fa= il_list); > + =A0 =A0validate_test_add("/visitor/input-strict/fail/union", > + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 &testdata, test_validate_fa= il_union); > + > + =A0 =A0g_test_run(); > + > + =A0 =A0return 0; > +} > diff --git a/tests/Makefile b/tests/Makefile > index c78ade1..31349f7 100644 > --- a/tests/Makefile > +++ b/tests/Makefile > @@ -15,7 +15,7 @@ check-qfloat: check-qfloat.o qfloat.o $(tools-obj-y) > =A0check-qjson: check-qjson.o $(qobject-obj-y) $(tools-obj-y) > =A0test-coroutine: test-coroutine.o qemu-timer-common.o async.o $(corouti= ne-obj-y) $(tools-obj-y) > > -test-qmp-input-visitor.o test-qmp-output-visitor.o \ > +test-qmp-input-visitor.o test-qmp-output-visitor.o test-qmp-input-strict= .o \ > =A0test-string-input-visitor.o test-string-output-visitor.o \ > =A0 =A0 =A0 =A0test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS +=3D -I = $(qapi-dir) > > @@ -36,6 +36,9 @@ test-string-output-visitor: test-string-output-visitor.= o $(qobject-obj-y) $(qapi > =A0test-string-input-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types= .c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y) > =A0test-string-input-visitor: test-string-input-visitor.o $(qobject-obj-y= ) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/te= st-qapi-types.o > > +test-qmp-input-strict.o: $(addprefix $(qapi-dir)/, test-qapi-types.c tes= t-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y) > +test-qmp-input-strict: test-qmp-input-strict.o $(qobject-obj-y) $(qapi-o= bj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-ty= pes.o > + > =A0test-qmp-output-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c= test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y) > =A0test-qmp-output-visitor: test-qmp-output-visitor.o $(qobject-obj-y) $(= qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-q= api-types.o > > -- > 1.7.9.1 > > >