Skip to content

Commit cc8d8ea

Browse files
committed
fix xstrdup leak in parse_short_opt
1 parent 6f84262 commit cc8d8ea

File tree

8 files changed

+81
-1
lines changed

8 files changed

+81
-1
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
822822
TEST_BUILTINS_OBJS += test-pack-mtimes.o
823823
TEST_BUILTINS_OBJS += test-parse-options.o
824824
TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
825+
TEST_BUILTINS_OBJS += test-free-unknown-options.o
825826
TEST_BUILTINS_OBJS += test-partial-clone.o
826827
TEST_BUILTINS_OBJS += test-path-utils.o
827828
TEST_BUILTINS_OBJS += test-path-walk.o

parse-options.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,16 @@ static int has_subcommands(const struct option *options)
638638
return 0;
639639
}
640640

641+
static void set_strdup_fn(struct parse_opt_ctx_t *ctx, const struct option *options) {
642+
for (; options->type != OPTION_END; options++)
643+
;
644+
if (options->value && options->strdup_fn) {
645+
ctx->unknown_opts = options->value;
646+
ctx->strdup_fn = options->strdup_fn;
647+
return;
648+
}
649+
}
650+
641651
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
642652
int argc, const char **argv, const char *prefix,
643653
const struct option *options,
@@ -655,6 +665,7 @@ static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
655665
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
656666
ctx->flags = flags;
657667
ctx->has_subcommands = has_subcommands(options);
668+
set_strdup_fn(ctx, options);
658669
if (!ctx->has_subcommands && (flags & PARSE_OPT_SUBCOMMAND_OPTIONAL))
659670
BUG("Using PARSE_OPT_SUBCOMMAND_OPTIONAL without subcommands");
660671
if (ctx->has_subcommands) {
@@ -981,7 +992,11 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
981992
*
982993
* This is leaky, too bad.
983994
*/
984-
ctx->argv[0] = xstrdup(ctx->opt - 1);
995+
if (ctx->unknown_opts && ctx->strdup_fn) {
996+
ctx->argv[0] = ctx->strdup_fn(ctx->unknown_opts, ctx->opt - 1);
997+
} else {
998+
ctx->argv[0] = xstrdup(ctx->opt - 1);
999+
}
9851000
*(char *)ctx->argv[0] = '-';
9861001
goto unknown;
9871002
case PARSE_OPT_NON_OPTION:

parse-options.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
7777
typedef int parse_opt_subcommand_fn(int argc, const char **argv,
7878
const char *prefix, struct repository *repo);
7979

80+
typedef char *parse_opt_strdup_fn(void *value, const char *s);
81+
8082
/*
8183
* `type`::
8284
* holds the type of the option, you must have an OPTION_END last in your
@@ -165,6 +167,7 @@ struct option {
165167
parse_opt_ll_cb *ll_callback;
166168
intptr_t extra;
167169
parse_opt_subcommand_fn *subcommand_fn;
170+
parse_opt_strdup_fn *strdup_fn;
168171
};
169172

170173
#define OPT_BIT_F(s, l, v, h, b, f) { \
@@ -388,6 +391,12 @@ static char *parse_options_noop_ignored_value MAYBE_UNUSED;
388391
}
389392
#define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), 0)
390393

394+
#define OPT_UNKNOWN(v, fn) { \
395+
.type = OPTION_END, \
396+
.value = (v), \
397+
.strdup_fn = (fn), \
398+
}
399+
391400
/*
392401
* parse_options() will filter out the processed options and leave the
393402
* non-option arguments in argv[]. argv0 is assumed program name and
@@ -496,6 +505,9 @@ struct parse_opt_ctx_t {
496505
const char *prefix;
497506
const char **alias_groups; /* must be in groups of 3 elements! */
498507
struct parse_opt_cmdmode_list *cmdmode_list;
508+
509+
void *unknown_opts;
510+
parse_opt_strdup_fn *strdup_fn;
499511
};
500512

501513
void parse_options_start(struct parse_opt_ctx_t *ctx,

t/helper/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ test_tool_sources = [
3939
'test-pack-mtimes.c',
4040
'test-parse-options.c',
4141
'test-parse-pathspec-file.c',
42+
'test-free-unknown-options.c',
4243
'test-partial-clone.c',
4344
'test-path-utils.c',
4445
'test-path-walk.c',
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "git-compat-util.h"
2+
#include "parse-options.h"
3+
#include "setup.h"
4+
#include "strvec.h"
5+
6+
static const char *const free_unknown_options_usage[] = {
7+
"test-tool free-unknown-options",
8+
NULL
9+
};
10+
11+
int cmd__free_unknown_options(int argc, const char **argv) {
12+
struct strvec *unknown_opts = xmalloc(sizeof(struct strvec));
13+
strvec_init(unknown_opts);
14+
const char *prefix = setup_git_directory();
15+
16+
bool a, b;
17+
struct option options[] = {
18+
OPT_BOOL('a', "test-a", &a, N_("option a, only for test use")),
19+
OPT_BOOL('b', "test-b", &b, N_("option b, only for test use")),
20+
OPT_UNKNOWN(unknown_opts, (parse_opt_strdup_fn *)&strvec_push),
21+
};
22+
23+
parse_options(argc, argv, prefix, options,
24+
free_unknown_options_usage, PARSE_OPT_KEEP_UNKNOWN_OPT);
25+
26+
printf("a = %s\n", a? "true": "false");
27+
printf("b = %s\n", b? "true": "false");
28+
29+
int i;
30+
for (i = 0; i < unknown_opts->nr; i++) {
31+
printf("free unknown option: %s\n", unknown_opts->v[i]);
32+
}
33+
strvec_clear(unknown_opts);
34+
free(unknown_opts);
35+
}

t/helper/test-tool.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {
5151
{ "parse-options-flags", cmd__parse_options_flags },
5252
{ "parse-pathspec-file", cmd__parse_pathspec_file },
5353
{ "parse-subcommand", cmd__parse_subcommand },
54+
{ "free-unknown-options", cmd__free_unknown_options},
5455
{ "partial-clone", cmd__partial_clone },
5556
{ "path-utils", cmd__path_utils },
5657
{ "path-walk", cmd__path_walk },

t/helper/test-tool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ int cmd__parse_options(int argc, const char **argv);
4444
int cmd__parse_options_flags(int argc, const char **argv);
4545
int cmd__parse_pathspec_file(int argc, const char** argv);
4646
int cmd__parse_subcommand(int argc, const char **argv);
47+
int cmd__free_unknown_options(int argc, const char **argv);
4748
int cmd__partial_clone(int argc, const char **argv);
4849
int cmd__path_utils(int argc, const char **argv);
4950
int cmd__path_walk(int argc, const char **argv);

t/t0040-parse-options.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,18 @@ test_expect_success 'u16 limits range' '
822822
test_grep "value 65536 for option .u16. not in range \[0,65535\]" err
823823
'
824824

825+
cat >expect <<\EOF
826+
a = true
827+
b = true
828+
free unknown option: -c
829+
free unknown option: -d
830+
EOF
831+
832+
test_expect_success 'free unknown options' '
833+
test-tool free-unknown-options -ac -bd \
834+
>output 2>output.err &&
835+
test_cmp expect output &&
836+
test_must_be_empty output.err
837+
'
838+
825839
test_done

0 commit comments

Comments
 (0)