diff --git a/ipthelper/ip6tables.c b/ipthelper/ip6tables.c index 10fd748..5952f7e 100644 --- a/ipthelper/ip6tables.c +++ b/ipthelper/ip6tables.c @@ -216,7 +216,6 @@ static const unsigned int inverse_for_options[NUMBER_OF_OPT] = 0, }; -#define opts iptables_globals.opts #define prog_name iptables_globals.program_name #define prog_vers iptables_globals.program_version /* A few hardcoded protocols for 'all' and in case the user has no @@ -1439,10 +1438,27 @@ int do_command6(int argc, char *argv[], char **table, void **handle) demand-load a protocol. */ opterr = 0; - opts = xt_params->orig_opts; + /* Create a malloc'd copy of orig_opts */ + if (iptables_globals.opts == NULL) { + size_t num_opts = 0; + struct option *orig_opts = iptables_globals.orig_opts; + + /* Count the number of options (including the NULL terminator) */ + while (orig_opts[num_opts].name != NULL) { + num_opts++; + } + num_opts++; /* Include the NULL terminator */ + + /* Allocate memory and copy the options */ + iptables_globals.opts = malloc(num_opts * sizeof(struct option)); + if (iptables_globals.opts == NULL) { + xtables_error(OTHER_PROBLEM, "malloc failed for options array"); + } + memcpy(iptables_globals.opts, iptables_globals.orig_opts, num_opts * sizeof(struct option)); + } while ((cs.c = getopt_long(argc, argv, "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:bvnt:m:xc:g:46", - opts, NULL)) != -1) { + iptables_globals.opts?: iptables_globals.orig_opts, NULL)) != -1) { switch (cs.c) { /* * Command selection @@ -1774,7 +1790,7 @@ int do_command6(int argc, char *argv[], char **table, void **handle) continue; break; } - cs.invert = 2; + cs.invert = FALSE; } for (matchp = cs.matches; matchp; matchp = matchp->next) @@ -2017,5 +2033,11 @@ int do_command6(int argc, char *argv[], char **table, void **handle) free(dmasks); xtables_free_opts(1); + /* Free the malloc'd copy of opts if it was allocated */ + if (iptables_globals.opts != iptables_globals.orig_opts) { + free(iptables_globals.opts); + iptables_globals.opts = NULL; + } + return ret; } diff --git a/ipthelper/iptables.c b/ipthelper/iptables.c index 4c0e026..a852412 100644 --- a/ipthelper/iptables.c +++ b/ipthelper/iptables.c @@ -1017,10 +1017,28 @@ int do_command4(int argc, char *argv[], char **table, void **handle) demand-load a protocol. */ opterr = 0; - iptables_globals.opts = iptables_globals.orig_opts; + /* Create a malloc'd copy of orig_opts */ + if (iptables_globals.opts == NULL) { + size_t num_opts = 0; + struct option *orig_opts = iptables_globals.orig_opts; + + /* Count the number of options (including the NULL terminator) */ + while (orig_opts[num_opts].name != NULL) { + num_opts++; + } + num_opts++; /* Include the NULL terminator */ + + /* Allocate memory and copy the options */ + iptables_globals.opts = malloc(num_opts * sizeof(struct option)); + if (iptables_globals.opts == NULL) { + xtables_error(OTHER_PROBLEM, "malloc failed for options array"); + } + memcpy(iptables_globals.opts, iptables_globals.orig_opts, num_opts * sizeof(struct option)); + } while ((cs.c = getopt_long(argc, argv, "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:g:46", - iptables_globals.opts, NULL)) != -1) { + iptables_globals.opts?: iptables_globals.orig_opts, + NULL)) != -1) { switch (cs.c) { /* * Command selection @@ -1523,5 +1541,11 @@ int do_command4(int argc, char *argv[], char **table, void **handle) free(dmasks); //xtables_free_opts(1); + /* Free the malloc'd copy of opts if it was allocated */ + if (iptables_globals.opts != iptables_globals.orig_opts) { + free(iptables_globals.opts); + iptables_globals.opts = NULL; + } + return ret; } \ No newline at end of file diff --git a/ipthelper/xshared.c b/ipthelper/xshared.c index aaf42be..fa893b6 100644 --- a/ipthelper/xshared.c +++ b/ipthelper/xshared.c @@ -1,3 +1,7 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + #include #include #include @@ -14,10 +18,89 @@ #include #include #include +#if defined(__unix__) || defined(__APPLE__) +#include +#endif #include "xtables.h" #include #include "xshared.h" +#define XS_LONGOPTS_SCAN_LIMIT 4096U + +#if defined(__unix__) || defined(__APPLE__) +#define XS_HAVE_DLADDR 1 +#else +#define XS_HAVE_DLADDR 0 +#endif + +static size_t xs_longopts_count(const struct option *opts, const char *ext_name) +{ + size_t i; + + if (opts == NULL) + return 0; + + for (i = 0; i < XS_LONGOPTS_SCAN_LIMIT; ++i) { + if (opts[i].name == NULL) + return i; + } + + if (ext_name != NULL) + xtables_error(OTHER_PROBLEM, + "Extension \"%s\" returned an unterminated option table.", + ext_name); + + xtables_error(OTHER_PROBLEM, + "xtables option table is missing its terminator."); + return 0; +} + +#if XS_HAVE_DLADDR +static bool xs_option_name_pointer_is_valid(const char *name) +{ + Dl_info info; + + if (name == NULL) + return true; + + return dladdr((const void *)name, &info) != 0; +} +#else +static bool xs_option_name_pointer_is_valid(const char *name) +{ + if (name == NULL) + return true; + +#if INTPTR_MAX > 0xffffffff + if ((uintptr_t)name < 0x100000000ULL) + return false; +#endif + return true; +} +#endif + +static void xs_validate_new_longopts(struct option *opts, size_t start, + const char *ext_name) +{ + size_t total; + size_t i; + + if (opts == NULL || ext_name == NULL) + return; + + total = xs_longopts_count(opts, ext_name); + if (start >= total) + return; + + for (i = start; i < total; ++i) { + if (!xs_option_name_pointer_is_valid(opts[i].name)) { + xtables_error(OTHER_PROBLEM, + "Extension \"%s\" was built against an incompatible libxtables release (detected corrupt option metadata). Please rebuild the module.", + ext_name); + } + } +} + /* * Print out any special helps. A user might like to be able to add a --help * to the commandline, and see expected results. So we call help for all @@ -146,6 +229,7 @@ int command_default(struct iptables_command_state *cs, m = load_proto(cs); if (m != NULL) { size_t size; + size_t merge_start; cs->proto_used = 1; @@ -157,18 +241,22 @@ int command_default(struct iptables_command_state *cs, m->m->u.user.revision = m->revision; xs_init_match(m); + merge_start = xs_longopts_count(gl->opts, NULL); if (m->x6_options != NULL) gl->opts = xtables_options_xfrm(gl->orig_opts, gl->opts, m->x6_options, &m->option_offset); else - gl->opts = xtables_merge_options(gl->orig_opts, - gl->opts, - m->extra_opts, - &m->option_offset); + gl->opts = xs_merge_options(gl->orig_opts, + gl->opts, + m->extra_opts, + &m->option_offset); if (gl->opts == NULL) xtables_error(OTHER_PROBLEM, "can't alloc memory!"); + xs_validate_new_longopts(gl->opts, merge_start, + m->real_name != NULL ? + m->real_name : m->name); optind--; /* Indicate to rerun getopt *immediately* */ return 1; @@ -564,18 +652,26 @@ void command_match(struct iptables_command_state *cs) if (m == m->next) return; /* Merge options for non-cloned matches */ - if (m->x6_options != NULL){ - opts = xtables_options_xfrm(xt_params->orig_opts, opts, + { + size_t merge_start = xs_longopts_count(opts, NULL); + + if (m->x6_options != NULL) { + opts = xtables_options_xfrm(xt_params->orig_opts, opts, m->x6_options, &m->option_offset); -int num_orig; -for (num_orig = 0; opts[num_orig].name != NULL; ++num_orig) {} + } else if (m->extra_opts != NULL) { + opts = xs_merge_options(xt_params->orig_opts, opts, + m->extra_opts, &m->option_offset); + + } else + return; + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); + + xs_validate_new_longopts(opts, merge_start, + m->real_name != NULL ? + m->real_name : m->name); } - else if (m->extra_opts != NULL) - opts = xtables_merge_options(xt_params->orig_opts, opts, - m->extra_opts, &m->option_offset); - if (opts == NULL) - xtables_error(OTHER_PROBLEM, "can't alloc memory!"); xt_params->opts = opts; } @@ -628,15 +724,85 @@ void command_jump(struct iptables_command_state *cs) cs->target->t->u.user.revision = cs->target->revision; xs_init_target(cs->target); - if (cs->target->x6_options != NULL) + size_t merge_start = xs_longopts_count(opts, NULL); + + if (cs->target->x6_options != NULL) { opts = xtables_options_xfrm(xt_params->orig_opts, opts, - cs->target->x6_options, - &cs->target->option_offset); - else - opts = xtables_merge_options(xt_params->orig_opts, opts, + cs->target->x6_options, + &cs->target->option_offset); + } else if (cs->target->extra_opts != NULL) + opts = xs_merge_options(xt_params->orig_opts, opts, cs->target->extra_opts, &cs->target->option_offset); + else + return; if (opts == NULL) xtables_error(OTHER_PROBLEM, "can't alloc memory!"); + xs_validate_new_longopts(opts, merge_start, + cs->target->real_name != NULL ? + cs->target->real_name : cs->jumpto); xt_params->opts = opts; } + + +struct option * +xs_merge_options(struct option *orig_opts, struct option *oldopts, + const struct option *newopts, unsigned int *option_offset) +{ + unsigned int num_orig = 0, num_old = 0, num_new = 0, i; + struct option *merge; + struct option *mp; + struct option *old_base = oldopts; + + if (newopts == NULL || newopts->name == NULL) + return oldopts; + + if (orig_opts != NULL) + for (; orig_opts[num_orig].name != NULL; ++num_orig) + ; + + if (oldopts != NULL) + for (; oldopts[num_old].name != NULL; ++num_old) + ; + + for (; newopts[num_new].name != NULL; ++num_new) + ; + + if (oldopts != NULL && num_old >= num_orig) { + oldopts += num_orig; + num_old -= num_orig; + } else { + oldopts = NULL; + num_old = 0; + } + + merge = malloc(sizeof(*merge) * (num_orig + num_new + num_old + 1)); + if (merge == NULL) + return NULL; + + if (num_orig != 0) + memcpy(merge, orig_opts, sizeof(*merge) * num_orig); + mp = merge + num_orig; + + xt_params->option_offset += XT_OPTION_OFFSET_SCALE; + *option_offset = xt_params->option_offset; + + for (i = 0; i < num_new; ++i, ++mp) { + mp->name = newopts[i].name; + mp->has_arg = newopts[i].has_arg; + mp->flag = newopts[i].flag; + mp->val = newopts[i].val + *option_offset; + } + + if (oldopts != NULL && num_old != 0) { + memcpy(mp, oldopts, sizeof(*mp) * num_old); + mp += num_old; + } + + memset(mp, 0, sizeof(*mp)); + + if (old_base != NULL && old_base != orig_opts && old_base != xt_params->orig_opts) + free(old_base); + + return merge; +} \ No newline at end of file diff --git a/ipthelper/xshared.h b/ipthelper/xshared.h index 52a91b2..16073a2 100644 --- a/ipthelper/xshared.h +++ b/ipthelper/xshared.h @@ -179,5 +179,9 @@ void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags, void command_match(struct iptables_command_state *cs); const char *xt_parse_target(const char *targetname); void command_jump(struct iptables_command_state *cs); +struct option *xs_merge_options(struct option *orig_opts, + struct option *oldopts, + const struct option *newopts, + unsigned int *option_offset); #endif /* IPTABLES_XSHARED_H */ diff --git a/ipthelper/xtoptions.c b/ipthelper/xtoptions.c index d329f2f..d0af962 100644 --- a/ipthelper/xtoptions.c +++ b/ipthelper/xtoptions.c @@ -120,7 +120,8 @@ xtables_options_xfrm(struct option *orig_opts, struct option *oldopts, memcpy(mp, oldopts, sizeof(*mp) * num_old); mp += num_old; } - xtables_free_opts(0); + if (xt_params->opts != NULL && xt_params->opts != xt_params->orig_opts) + xtables_free_opts(0); /* Clear trailing entry */ memset(mp, 0, sizeof(*mp));