From 5dc41f828398aa0a65c3f536577ee43f990e1700 Mon Sep 17 00:00:00 2001 From: Armaan Sandhu Date: Sat, 8 Aug 2026 20:46:58 -0400 Subject: [PATCH] options: reject an empty long option name An argument of the form "--=VALUE" leaves an empty option name, which startswith() treats as a prefix of every defined option. Depending on how many long options a namespace defines that yields either an ambiguity error listing every option, or, with exactly one option defined, a unique partial match that silently parses "--=VALUE" as that option. --- src/shared/options.c | 10 ++++++++++ src/test/test-options.c | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/shared/options.c b/src/shared/options.c index 68a275037c..41f9c6fd28 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -223,6 +223,16 @@ int option_parse( /* argument (if any) is separate */ optname = state->argv[state->optind]; + /* An empty name, i.e. "--=…", would be a prefix of every option, so reject it + * right away rather than let it match. */ + if (isempty(optname + 2)) { + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + goto fail; + } + const Option *last_partial = NULL; unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ diff --git a/src/test/test-options.c b/src/test/test-options.c index 1e4a02ea03..867c76a8c4 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -1580,4 +1580,24 @@ TEST(option_get_synopsis) { test_option_get_synopsis_one(&(const Option) { 0, OPTION_POSITIONAL_ENTRY, 'u', "(fixed)", "unused" }, "/", true, "(fixed)"); } +TEST(option_empty_long_name) { + static const Option one_option[] = { + { 1, .short_code = 'o', .long_code = "output", .metavar = "ARG" }, + {} + }; + + static const Option two_options[] = { + { 1, .short_code = 'o', .long_code = "output", .metavar = "ARG" }, + { 2, .long_code = "help" }, + {} + }; + + /* "--=…" carries an empty long option name, which must not prefix-match every defined option. + * With a single option defined that would otherwise look like a unique partial match. */ + test_option_invalid_one(STRV_MAKE("arg0", "--=foo"), one_option); + test_option_invalid_one(STRV_MAKE("arg0", "--="), one_option); + test_option_invalid_one(STRV_MAKE("arg0", "--=foo"), two_options); + test_option_invalid_one(STRV_MAKE("arg0", "--="), two_options); +} + DEFINE_TEST_MAIN(LOG_DEBUG);