From: Bhargava Shastry <bshas...@sect.tu-berlin.de>

 The fuzzer target, expr_parse_target.c, comprises test cases adapted
 from test-ovn.c.

 In addition, this patch contains configuration files for oss-fuzz,
 including a dictionary, expr.dict, to aid quick path discovery and a
 fuzzer configuration file that customises fuzzing for this target.

 Prominently, the patch sets the maximum length of fuzzed input 
 (the string accepted by lexer/expression parser) to be up to 100 
 characters long not containing a newline character.

Signed-off-by: Bhargava Shastry <bshastry at sect.tu-berlin.de>
---
 tests/oss-fuzz/automake.mk                    |  14 +-
 tests/oss-fuzz/config/expr.dict               | 120 +++++
 .../oss-fuzz/config/expr_parse_target.options |   3 +
 tests/oss-fuzz/expr_parse_target.c            | 479 ++++++++++++++++++
 4 files changed, 614 insertions(+), 2 deletions(-)
 create mode 100644 tests/oss-fuzz/config/expr.dict
 create mode 100644 tests/oss-fuzz/config/expr_parse_target.options
 create mode 100644 tests/oss-fuzz/expr_parse_target.c

diff --git a/tests/oss-fuzz/automake.mk b/tests/oss-fuzz/automake.mk
index 9f46de6d0..3e3ac2f9c 100644
--- a/tests/oss-fuzz/automake.mk
+++ b/tests/oss-fuzz/automake.mk
@@ -1,7 +1,8 @@
 OSS_FUZZ_TARGETS = \
        tests/oss-fuzz/flow_extract_target \
        tests/oss-fuzz/json_parser_target \
-       tests/oss-fuzz/ofp_print_target
+       tests/oss-fuzz/ofp_print_target \
+       tests/oss-fuzz/expr_parse_target
 EXTRA_PROGRAMS += $(OSS_FUZZ_TARGETS)
 oss-fuzz-targets: $(OSS_FUZZ_TARGETS)
 
@@ -23,8 +24,17 @@ tests_oss_fuzz_ofp_print_target_SOURCES = \
 tests_oss_fuzz_ofp_print_target_LDADD = lib/libopenvswitch.la
 tests_oss_fuzz_ofp_print_target_LDFLAGS = $(LIB_FUZZING_ENGINE) -lc++
 
+tests_oss_fuzz_expr_parse_target_SOURCES = \
+        tests/oss-fuzz/expr_parse_target.c \
+        tests/oss-fuzz/fuzzer.h
+tests_oss_fuzz_expr_parse_target_LDADD = lib/libopenvswitch.la \
+                                         ovn/lib/libovn.la
+tests_oss_fuzz_expr_parse_target_LDFLAGS = $(LIB_FUZZING_ENGINE) -lc++
+
 EXTRA_DIST += \
        tests/oss-fuzz/config/flow_extract_target.options \
        tests/oss-fuzz/config/json_parser_target.options \
        tests/oss-fuzz/config/ofp_print_target.options \
-       tests/oss-fuzz/config/ovs.dict
+       tests/oss-fuzz/config/expr_parse_target.options \
+       tests/oss-fuzz/config/ovs.dict \
+       tests/oss-fuzz/config/expr.dict
diff --git a/tests/oss-fuzz/config/expr.dict b/tests/oss-fuzz/config/expr.dict
new file mode 100644
index 000000000..03741ad7d
--- /dev/null
+++ b/tests/oss-fuzz/config/expr.dict
@@ -0,0 +1,120 @@
+" = "
+" = ("
+" = dns_lookup();"
+" { "
+" };"
+"!"
+"!="
+"$"
+"&&"
+"("
+"()"
+")"
+"),commit,table=,zone=NXM_NX_REG)"
+");"
+", "
+", meter=\"\""
+","
+",bucket=bucket_id=,weight:100,actions=ct(nat(dst="
+"--"
+".."
+"/"
+"/%"
+"0"
+":"
+"<"
+"<->"
+"<="
+"="
+"=="
+"=r"
+">"
+">="
+"["
+"\x00"
+"\x28"
+"]"
+"]:"
+"allow"
+"arp"
+"bool"
+"bswap "
+"bswap %0"
+"cc"
+"clone"
+"ct_clear"
+"ct_clear;"
+"ct_commit"
+"ct_commit("
+"ct_dnat"
+"ct_label"
+"ct_label="
+"ct_lb"
+"ct_mark"
+"ct_mark="
+"ct_mark=%#x"
+"ct_next"
+"ct_next;"
+"ct_snat"
+"decimal"
+"dhcpv6_stateful"
+"dhcpv6_stateless"
+"dns_lookup"
+"drop"
+"drop;"
+"egress"
+"error("
+"get_arp"
+"get_nd"
+"hexadecimal"
+"icmp4"
+"icmp6"
+"ingress"
+"ip.ttl"
+"ip.ttl--;"
+"ipv4"
+"ipv6"
+"log"
+"log("
+"mac"
+"meter"
+"name"
+"name=\"\", "
+"nd_na"
+"nd_na_router"
+"nd_ns"
+"next"
+"next();"
+"next(pipeline=, table=);"
+"next;"
+"output"
+"output;"
+"pipeline"
+"put_arp"
+"put_dhcp_opts"
+"put_dhcpv6_opts"
+"put_nd"
+"put_nd_ra_opts"
+"reject"
+"set_meter"
+"set_meter();"
+"set_meter(, );"
+"set_meter(,);"
+"set_queue"
+"set_queue();"
+"severity"
+"severity="
+"slaac"
+"static_routes"
+"str"
+"table"
+"tcp_reset"
+"type=select,selection_method=dp_hash"
+"uint16"
+"uint32"
+"uint8"
+"verdict"
+"verdict=, "
+"{"
+"||"
+"}"
diff --git a/tests/oss-fuzz/config/expr_parse_target.options 
b/tests/oss-fuzz/config/expr_parse_target.options
new file mode 100644
index 000000000..c0dfa68f1
--- /dev/null
+++ b/tests/oss-fuzz/config/expr_parse_target.options
@@ -0,0 +1,3 @@
+dict = expr.dict
+close_fd_mask = 3
+max_len = 100
diff --git a/tests/oss-fuzz/expr_parse_target.c 
b/tests/oss-fuzz/expr_parse_target.c
new file mode 100644
index 000000000..e7dee9415
--- /dev/null
+++ b/tests/oss-fuzz/expr_parse_target.c
@@ -0,0 +1,479 @@
+#include <config.h>
+#include "fuzzer.h"
+#include <errno.h>
+#include <getopt.h>
+#include <sys/wait.h>
+
+#include "command-line.h"
+#include "dp-packet.h"
+#include "fatal-signal.h"
+#include "flow.h"
+#include "openvswitch/dynamic-string.h"
+#include "openvswitch/match.h"
+#include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/vlog.h"
+#include "ovn/actions.h"
+#include "ovn/expr.h"
+#include "ovn/lex.h"
+#include "ovn/lib/logical-fields.h"
+#include "ovn/lib/ovn-l7.h"
+#include "ovn/lib/extend-table.h"
+#include "openvswitch/shash.h"
+#include "simap.h"
+#include "util.h"
+
+static void
+compare_token(const struct lex_token *a, const struct lex_token *b)
+{
+    if (a->type != b->type) {
+        fprintf(stderr, "type differs: %d -> %d\n", a->type, b->type);
+        return;
+    }
+
+    if (!((a->s && b->s && !strcmp(a->s, b->s))
+          || (!a->s && !b->s))) {
+        fprintf(stderr, "string differs: %s -> %s\n",
+                a->s ? a->s : "(null)",
+                b->s ? b->s : "(null)");
+        return;
+    }
+
+    if (a->type == LEX_T_INTEGER || a->type == LEX_T_MASKED_INTEGER) {
+        if (memcmp(&a->value, &b->value, sizeof a->value)) {
+            fprintf(stderr, "value differs\n");
+            return;
+        }
+
+        if (a->type == LEX_T_MASKED_INTEGER
+            && memcmp(&a->mask, &b->mask, sizeof a->mask)) {
+            fprintf(stderr, "mask differs\n");
+            return;
+        }
+
+        if (a->format != b->format
+            && !(a->format == LEX_F_HEXADECIMAL
+                 && b->format == LEX_F_DECIMAL
+                 && a->value.integer == 0)) {
+            fprintf(stderr, "format differs: %d -> %d\n",
+                    a->format, b->format);
+        }
+    }
+}
+
+static void
+test_lex(struct ds *input)
+{
+    struct ds output;
+
+    ds_init(&output);
+    struct lexer lexer;
+
+    lexer_init(&lexer, ds_cstr(input));
+    ds_clear(&output);
+    while (lexer_get(&lexer) != LEX_T_END) {
+        size_t len = output.length;
+        lex_token_format(&lexer.token, &output);
+
+        /* Check that the formatted version can really be parsed back
+         * losslessly. */
+        if (lexer.token.type != LEX_T_ERROR) {
+            const char *s = ds_cstr(&output) + len;
+            struct lexer l2;
+
+            lexer_init(&l2, s);
+            lexer_get(&l2);
+            compare_token(&lexer.token, &l2.token);
+            lexer_destroy(&l2);
+        }
+        ds_put_char(&output, ' ');
+    }
+    lexer_destroy(&lexer);
+
+    ds_chomp(&output, ' ');
+    puts(ds_cstr(&output));
+    ds_destroy(&output);
+}
+
+static void
+create_symtab(struct shash *symtab)
+{
+    ovn_init_symtab(symtab);
+
+    /* For negative testing. */
+    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
+    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
+                          "self_recurse != 0", false);
+    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
+                          "mutual_recurse_2 != 0", false);
+    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
+                          "mutual_recurse_1 != 0", false);
+    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
+}
+
+static void
+create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
+                struct hmap *nd_ra_opts)
+{
+    hmap_init(dhcp_opts);
+    dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
+    dhcp_opt_add(dhcp_opts, "netmask", 1, "ipv4");
+    dhcp_opt_add(dhcp_opts, "router",  3, "ipv4");
+    dhcp_opt_add(dhcp_opts, "dns_server", 6, "ipv4");
+    dhcp_opt_add(dhcp_opts, "log_server", 7, "ipv4");
+    dhcp_opt_add(dhcp_opts, "lpr_server",  9, "ipv4");
+    dhcp_opt_add(dhcp_opts, "domain", 15, "str");
+    dhcp_opt_add(dhcp_opts, "swap_server", 16, "ipv4");
+    dhcp_opt_add(dhcp_opts, "policy_filter", 21, "ipv4");
+    dhcp_opt_add(dhcp_opts, "router_solicitation",  32, "ipv4");
+    dhcp_opt_add(dhcp_opts, "nis_server", 41, "ipv4");
+    dhcp_opt_add(dhcp_opts, "ntp_server", 42, "ipv4");
+    dhcp_opt_add(dhcp_opts, "server_id",  54, "ipv4");
+    dhcp_opt_add(dhcp_opts, "tftp_server", 66, "ipv4");
+    dhcp_opt_add(dhcp_opts, "classless_static_route", 121, "static_routes");
+    dhcp_opt_add(dhcp_opts, "ip_forward_enable",  19, "bool");
+    dhcp_opt_add(dhcp_opts, "router_discovery", 31, "bool");
+    dhcp_opt_add(dhcp_opts, "ethernet_encap", 36, "bool");
+    dhcp_opt_add(dhcp_opts, "default_ttl",  23, "uint8");
+    dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8");
+    dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16");
+    dhcp_opt_add(dhcp_opts, "lease_time",  51, "uint32");
+    dhcp_opt_add(dhcp_opts, "wpad", 252, "str");
+
+    /* DHCPv6 options. */
+    hmap_init(dhcpv6_opts);
+    dhcp_opt_add(dhcpv6_opts, "server_id",  2, "mac");
+    dhcp_opt_add(dhcpv6_opts, "ia_addr",  5, "ipv6");
+    dhcp_opt_add(dhcpv6_opts, "dns_server",  23, "ipv6");
+    dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
+
+    /* IPv6 ND RA options. */
+    hmap_init(nd_ra_opts);
+    nd_ra_opts_init(nd_ra_opts);
+}
+
+static void
+create_addr_sets(struct shash *addr_sets)
+{
+    shash_init(addr_sets);
+
+    static const char *const addrs1[] = {
+        "10.0.0.1", "10.0.0.2", "10.0.0.3",
+    };
+    static const char *const addrs2[] = {
+        "::1", "::2", "::3",
+    };
+    static const char *const addrs3[] = {
+        "00:00:00:00:00:01", "00:00:00:00:00:02", "00:00:00:00:00:03",
+    };
+    static const char *const addrs4[] = { NULL };
+
+    expr_const_sets_add(addr_sets, "set1", addrs1, 3, true);
+    expr_const_sets_add(addr_sets, "set2", addrs2, 3, true);
+    expr_const_sets_add(addr_sets, "set3", addrs3, 3, true);
+    expr_const_sets_add(addr_sets, "set4", addrs4, 0, true);
+}
+
+static void
+create_port_groups(struct shash *port_groups)
+{
+    shash_init(port_groups);
+
+    static const char *const pg1[] = {
+        "lsp1", "lsp2", "lsp3",
+    };
+    static const char *const pg2[] = { NULL };
+
+    expr_const_sets_add(port_groups, "pg1", pg1, 3, false);
+    expr_const_sets_add(port_groups, "pg_empty", pg2, 0, false);
+}
+
+static bool
+lookup_port_cb(const void *ports_, const char *port_name, unsigned int *portp)
+{
+    const struct simap *ports = ports_;
+    const struct simap_node *node = simap_find(ports, port_name);
+    if (!node) {
+        return false;
+    }
+    *portp = node->data;
+    return true;
+}
+
+static bool
+is_chassis_resident_cb(const void *ports_, const char *port_name)
+{
+    const struct simap *ports = ports_;
+    const struct simap_node *node = simap_find(ports, port_name);
+    if (node) {
+        return true;
+    }
+    return false;
+}
+
+static void
+test_parse_actions(struct ds *input)
+{
+    struct shash symtab;
+    struct hmap dhcp_opts;
+    struct hmap dhcpv6_opts;
+    struct hmap nd_ra_opts;
+    struct simap ports;
+
+    create_symtab(&symtab);
+    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);
+
+    /* Initialize group ids. */
+    struct ovn_extend_table group_table;
+    ovn_extend_table_init(&group_table);
+
+    /* Initialize meter ids for QoS. */
+    struct ovn_extend_table meter_table;
+    ovn_extend_table_init(&meter_table);
+
+    simap_init(&ports);
+    simap_put(&ports, "eth0", 5);
+    simap_put(&ports, "eth1", 6);
+    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
+
+    struct ofpbuf ovnacts;
+    struct expr *prereqs;
+    char *error;
+
+    puts(ds_cstr(input));
+
+    ofpbuf_init(&ovnacts, 0);
+
+    const struct ovnact_parse_params pp = {
+        .symtab = &symtab,
+        .dhcp_opts = &dhcp_opts,
+        .dhcpv6_opts = &dhcpv6_opts,
+        .nd_ra_opts = &nd_ra_opts,
+        .n_tables = 24,
+        .cur_ltable = 10,
+    };
+    error = ovnacts_parse_string(ds_cstr(input), &pp, &ovnacts, &prereqs);
+    if (!error) {
+        /* Convert the parsed representation back to a string and print it,
+         * if it's different from the input. */
+        struct ds ovnacts_s = DS_EMPTY_INITIALIZER;
+        ovnacts_format(ovnacts.data, ovnacts.size, &ovnacts_s);
+        if (strcmp(ds_cstr(input), ds_cstr(&ovnacts_s))) {
+            printf("    formats as %s\n", ds_cstr(&ovnacts_s));
+        }
+
+        /* Encode the actions into OpenFlow and print. */
+        const struct ovnact_encode_params ep = {
+            .lookup_port = lookup_port_cb,
+            .aux = &ports,
+            .is_switch = true,
+            .group_table = &group_table,
+            .meter_table = &meter_table,
+
+            .pipeline = OVNACT_P_INGRESS,
+            .ingress_ptable = 8,
+            .egress_ptable = 40,
+            .output_ptable = 64,
+            .mac_bind_ptable = 65,
+        };
+        struct ofpbuf ofpacts;
+        ofpbuf_init(&ofpacts, 0);
+        ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
+        struct ds ofpacts_s = DS_EMPTY_INITIALIZER;
+        struct ofpact_format_params fp = { .s = &ofpacts_s };
+        ofpacts_format(ofpacts.data, ofpacts.size, &fp);
+        printf("    encodes as %s\n", ds_cstr(&ofpacts_s));
+        ds_destroy(&ofpacts_s);
+        ofpbuf_uninit(&ofpacts);
+
+        /* Print prerequisites if any. */
+        if (prereqs) {
+            struct ds prereqs_s = DS_EMPTY_INITIALIZER;
+            expr_format(prereqs, &prereqs_s);
+            printf("    has prereqs %s\n", ds_cstr(&prereqs_s));
+            ds_destroy(&prereqs_s);
+        }
+
+        /* Now re-parse and re-format the string to verify that it's
+         * round-trippable. */
+        struct ofpbuf ovnacts2;
+        struct expr *prereqs2;
+        ofpbuf_init(&ovnacts2, 0);
+        error = ovnacts_parse_string(ds_cstr(&ovnacts_s), &pp, &ovnacts2,
+                                     &prereqs2);
+        if (!error) {
+            struct ds ovnacts2_s = DS_EMPTY_INITIALIZER;
+            ovnacts_format(ovnacts2.data, ovnacts2.size, &ovnacts2_s);
+            if (strcmp(ds_cstr(&ovnacts_s), ds_cstr(&ovnacts2_s))) {
+                printf("    bad reformat: %s\n", ds_cstr(&ovnacts2_s));
+            }
+            ds_destroy(&ovnacts2_s);
+        } else {
+            printf("    reparse error: %s\n", error);
+            free(error);
+        }
+        expr_destroy(prereqs2);
+
+        ovnacts_free(ovnacts2.data, ovnacts2.size);
+        ofpbuf_uninit(&ovnacts2);
+        ds_destroy(&ovnacts_s);
+    } else {
+        printf("    %s\n", error);
+        free(error);
+    }
+
+    expr_destroy(prereqs);
+    ovnacts_free(ovnacts.data, ovnacts.size);
+    ofpbuf_uninit(&ovnacts);
+
+    simap_destroy(&ports);
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+    dhcp_opts_destroy(&dhcp_opts);
+    dhcp_opts_destroy(&dhcpv6_opts);
+    nd_ra_opts_destroy(&nd_ra_opts);
+    ovn_extend_table_destroy(&group_table);
+    ovn_extend_table_destroy(&meter_table);
+}
+
+static void test_parse_expr(struct ds *input, int steps)
+{
+    struct shash symtab;
+    struct shash addr_sets;
+    struct shash port_groups;
+    struct simap ports;
+    struct expr *expr;
+    char *error;
+
+    create_symtab(&symtab);
+    create_addr_sets(&addr_sets);
+    create_port_groups(&port_groups);
+
+    simap_init(&ports);
+    simap_put(&ports, "eth0", 5);
+    simap_put(&ports, "eth1", 6);
+    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
+    simap_put(&ports, "lsp1", 0x11);
+    simap_put(&ports, "lsp2", 0x12);
+    simap_put(&ports, "lsp3", 0x13);
+
+    expr = expr_parse_string(ds_cstr(input), &symtab, &addr_sets,
+                             &port_groups, &error);
+    if (!error && steps > 0) {
+        expr = expr_annotate(expr, &symtab, &error);
+    }
+    if (!error) {
+        if (steps > 1) {
+            expr = expr_simplify(expr, is_chassis_resident_cb, &ports);
+        }
+        if (steps > 2) {
+            expr = expr_normalize(expr);
+            ovs_assert(expr_is_normalized(expr));
+        }
+    }
+    if (!error) {
+        if (steps > 3) {
+            struct hmap matches;
+
+            expr_to_matches(expr, lookup_port_cb, &ports, &matches);
+            expr_matches_print(&matches, stdout);
+            expr_matches_destroy(&matches);
+        } else {
+            struct ds output = DS_EMPTY_INITIALIZER;
+            expr_format(expr, &output);
+            puts(ds_cstr(&output));
+            ds_destroy(&output);
+        }
+    } else {
+        puts(error);
+        free(error);
+    }
+    expr_destroy(expr);
+    simap_destroy(&ports);
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+    expr_const_sets_destroy(&addr_sets);
+    shash_destroy(&addr_sets);
+    expr_const_sets_destroy(&port_groups);
+    shash_destroy(&port_groups);
+}
+
+static bool
+lookup_atoi_cb(const void *aux OVS_UNUSED, const char *port_name,
+               unsigned int *portp)
+{
+    *portp = atoi(port_name);
+    return true;
+}
+
+static void
+test_expr_to_packets(struct ds *input)
+{
+    struct shash symtab;
+    create_symtab(&symtab);
+
+    struct flow uflow;
+    char *error = expr_parse_microflow(ds_cstr(input), &symtab, NULL, NULL, 
lookup_atoi_cb, NULL, &uflow);
+    if (error) {
+        puts(error);
+        free(error);
+        expr_symtab_destroy(&symtab);
+        shash_destroy(&symtab);
+        return;
+    }
+
+    uint64_t packet_stub[128 / 8];
+    struct dp_packet packet;
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    flow_compose(&packet, &uflow, NULL, 64);
+
+    struct ds output = DS_EMPTY_INITIALIZER;
+    const uint8_t *buf = dp_packet_data(&packet);
+    for (int i = 0; i < dp_packet_size(&packet); i++) {
+        uint8_t val = buf[i];
+        ds_put_format(&output, "%02"PRIx8, val);
+    }
+    puts(ds_cstr(&output));
+    ds_destroy(&output);
+    dp_packet_uninit(&packet);
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+    struct ds input;
+
+    /* Bail out if we cannot construct at least a 1 char string. */
+    if ((size < 2) || (data[size-1] != '\0')) {
+        return 0;
+    }
+
+    /* Disable logging to avoid write to disk. */
+    static bool isInit = false;
+    if (!isInit) {
+        vlog_set_verbosity("off");
+        isInit = true;
+    }
+
+    ds_init(&input);
+    ds_put_cstr(&input, (const char *)data);
+
+    char *isNewLine = strpbrk(ds_cstr(&input), "\n");
+    if (isNewLine) {
+        ds_destroy(&input);
+        return 0;
+    }
+
+    /* Parse, annotate, simplify, normalize expr and convert to flows. */
+    test_parse_expr(&input, 4);
+    /* Parse actions. */
+    test_parse_actions(&input);
+    /* Test OVN lexer. */
+    test_lex(&input);
+    /* Expr to packets. */
+    test_expr_to_packets(&input);
+    ds_destroy(&input);
+    return 0;
+}
-- 
2.17.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to