From: Patrick Steinhardt <ps@pks.im>
To: git@vger.kernel.org
Cc: "John Paul Adrian Glaubitz" <glaubitz@physik.fu-berlin.de>,
"Todd Zullinger" <tmz@pobox.com>, "René Scharfe" <l.s.r@web.de>,
"SZEDER Gábor" <szeder.dev@gmail.com>,
"Derrick Stolee" <stolee@gmail.com>, "Jeff King" <peff@peff.net>,
"Phillip Wood" <phillip.wood123@gmail.com>
Subject: [PATCH v3 0/7] parse-options: harden handling of integer values
Date: Wed, 16 Apr 2025 12:02:09 +0200 [thread overview]
Message-ID: <20250416-b4-pks-parse-options-integers-v3-0-d390746bea79@pks.im> (raw)
In-Reply-To: <20250401-b4-pks-parse-options-integers-v1-0-a628ad40c3b4@pks.im>
Hi,
this patch series addresses the issues raised in [1] and [2]. As
discussed in [1], the series also introduces a couple of safeguards to
make it harder to misuse `OPT_INTEGER()` and `OPT_MAGNITUDE()`:
- We now track the precision of the underlying integer types. This
makes it possible to pass arbitrarily-sized integers to those
options, not only `int` and `unsigned long`, respectively.
- We introduce a build assert to verify that the passed variable has
correct signedness.
Furthermore, the series introduces `OPT_UNSIGNED()` to adapt all
callsites that previously used variables with the wrong signedness.
Changes in v2:
- Adapt computation of upper bounds to use similar logic to
`maximum_signed_value_of_type()`.
- Link to v1: https://lore.kernel.org/r/20250401-b4-pks-parse-options-integers-v1-0-a628ad40c3b4@pks.im
Changes in v3:
- Introduce `errno` checks for `strto{u,i}max()`.
- Note that the precision is in bytes.
- Reject leading '-' when parsing unsigned integers.
- Introduce bounded integer options. This patch is mostly a proof of
concept that demonstrates that precision and ranges are orthogonal
to one another, so I consider it to be an optional patch. It may be
useful in the future, but I haven't converted any callsites to use
bounds yet.
- Link to v2: https://lore.kernel.org/r/20250415-b4-pks-parse-options-integers-v2-0-ce07441a1f01@pks.im
Thanks!
Patrick
[1]: <89257ab82cd60d135cce02d51eacee7ec35c1c37.camel@physik.fu-berlin.de>
[2]: <Z8HW6petWuMRWSXf@teonanacatl.net>
---
Patrick Steinhardt (7):
global: use designated initializers for options
parse-options: check for overflow when parsing integers
parse-options: introduce precision handling for `OPTION_INTEGER`
parse-options: introduce precision handling for `OPTION_MAGNITUDE`
parse-options: introduce `OPTION_UNSIGNED`
parse-options: detect mismatches in integer signedness
parse-options: introduce bounded integer options
apply.c | 4 +-
archive.c | 35 ++++++---
builtin/am.c | 28 +++++--
builtin/backfill.c | 4 +-
builtin/clone.c | 13 +++-
builtin/column.c | 2 +-
builtin/commit-tree.c | 12 ++-
builtin/commit.c | 62 +++++++++++----
builtin/config.c | 13 +++-
builtin/describe.c | 24 ++++--
builtin/fetch.c | 10 ++-
builtin/fmt-merge-msg.c | 27 +++++--
builtin/gc.c | 12 ++-
builtin/grep.c | 18 +++--
builtin/init-db.c | 13 +++-
builtin/ls-remote.c | 11 ++-
builtin/merge.c | 38 +++++++--
builtin/read-tree.c | 11 ++-
builtin/rebase.c | 25 ++++--
builtin/revert.c | 12 ++-
builtin/show-branch.c | 13 +++-
builtin/tag.c | 24 ++++--
builtin/update-index.c | 131 +++++++++++++++++++++----------
builtin/write-tree.c | 12 ++-
diff.c | 13 +++-
git-compat-util.h | 7 ++
parse-options.c | 176 +++++++++++++++++++++++++++++++++++++-----
parse-options.h | 75 +++++++++++++++++-
ref-filter.h | 15 ++--
t/helper/test-parse-options.c | 51 ++++++++++--
t/t0040-parse-options.sh | 102 +++++++++++++++++++++++-
31 files changed, 805 insertions(+), 188 deletions(-)
Range-diff versus v2:
1: 80c8b943e9b = 1: 3dc362ae91b global: use designated initializers for options
-: ----------- > 2: a5a71ad6da8 parse-options: check for overflow when parsing integers
2: 307eaeb463c ! 3: 1ad133c639e parse-options: introduce precision handling for `OPTION_INTEGER`
@@ parse-options.c: static enum parse_opt_result do_get_value(struct parse_opt_ctx_
+ } else if (!*arg) {
return error(_("%s expects a numerical value"),
optname(opt, flags));
-- *(int *)opt->value = strtol(arg, (char **)&s, 10);
-- if (*s)
-- return error(_("%s expects a numerical value"),
-- optname(opt, flags));
-- return 0;
+ } else {
++ errno = 0;
+ value = strtoimax(arg, (char **)&s, 10);
+ if (*s)
+ return error(_("%s expects a numerical value"),
+ optname(opt, flags));
-+
++ if (errno == ERANGE)
++ return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
++ arg, optname(opt, flags), lower_bound, upper_bound);
++ if (errno)
++ return error_errno(_("value %s for %s cannot be parsed"),
++ arg, optname(opt, flags));
+ }
+- errno = 0;
+- *(int *)opt->value = strtol(arg, (char **)&s, 10);
+- if (*s)
+- return error(_("%s expects a numerical value"),
+- optname(opt, flags));
+- if (errno == ERANGE)
+ if (value < lower_bound || value > upper_bound)
-+ return error(_("value %"PRIdMAX" for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
-+ value, optname(opt, flags), lower_bound, upper_bound);
-+
+ return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
+- arg, optname(opt, flags), (intmax_t)LONG_MIN, (intmax_t)LONG_MAX);
+- if (errno)
+- return error_errno(_("value %s for %s cannot be parsed"),
+- arg, optname(opt, flags));
++ arg, optname(opt, flags), lower_bound, upper_bound);
+
+- return 0;
+ switch (opt->precision) {
+ case 1:
+ *(int8_t *)opt->value = value;
@@ parse-options.h: typedef int parse_opt_subcommand_fn(int argc, const char **argv
* stores pointers to the values to be filled.
*
+ * `precision`::
-+ * precision of the integer pointed to by `value`. Should typically be its
-+ * `sizeof()`.
++ * precision of the integer pointed to by `value` in number of bytes. Should
++ * typically be its `sizeof()`.
+ *
* `argh`::
* token to explain the kind of argument this option wants. Does not
@@ t/t0040-parse-options.sh: test_expect_success 'OPT_NUMBER_CALLBACK() works' '
magnitude: 0
timestamp: 0
string: (not set)
-@@ t/t0040-parse-options.sh: test_expect_success 'magnitude with units but no numbers' '
+@@ t/t0040-parse-options.sh: test_expect_success 'overflowing integer' '
test_must_be_empty out
'
3: 5bfb9af2262 ! 4: 01e56b43c77 parse-options: introduce precision handling for `OPTION_MAGNITUDE`
@@ Commit message
## parse-options.c ##
@@ parse-options.c: static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
+
+ if (value < lower_bound || value > upper_bound)
+ return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"),
+- arg, optname(opt, flags), lower_bound, upper_bound);
++ arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound);
+
+ switch (opt->precision) {
+ case 1:
+@@ parse-options.c: static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
}
}
case OPTION_MAGNITUDE:
@@ parse-options.c: static enum parse_opt_result do_get_value(struct parse_opt_ctx_
+ }
+
+ if (value > upper_bound)
-+ return error(_("value %"PRIuMAX" for %s exceeds %"PRIuMAX),
-+ (uintmax_t) value, optname(opt, flags), upper_bound);
++ return error(_("value %s for %s not in range [%"PRIuMAX",%"PRIuMAX"]"),
++ arg, optname(opt, flags), (uintmax_t)0, (uintmax_t)upper_bound);
+
+ switch (opt->precision) {
+ case 1:
@@ t/t0040-parse-options.sh: test_expect_success 'i16 limits range' '
+ test-tool parse-options --m16 65535 >out &&
+ test_grep "m16: 65535" out &&
+ test_must_fail test-tool parse-options --m16 65536 2>err &&
-+ test_grep "value 65536 for option .m16. exceeds 65535" err
++ test_grep "value 65536 for option .m16. not in range \[0,65535\]" err
+'
+
test_done
4: 75ff495b897 ! 5: e7458eadea9 parse-options: introduce `OPTION_UNSIGNED`
@@ Commit message
parse-options: introduce `OPTION_UNSIGNED`
We have two generic ways to parse integers in the "parse-options"
- subsytem:
+ subsystem:
- `OPTION_INTEGER` parses a signed integer.
@@ parse-options.c: static enum parse_opt_result do_get_value(struct parse_opt_ctx_
+ } else if (!*arg) {
+ return error(_("%s expects a numerical value"),
+ optname(opt, flags));
++ } else if (*arg == '-') {
++ return error(_("%s does not accept negative values"),
++ optname(opt, flags));
+ } else {
++ errno = 0;
+ value = strtoumax(arg, (char **)&s, 10);
+ if (*s)
+ return error(_("%s expects a numerical value"),
+ optname(opt, flags));
++ if (errno == ERANGE)
++ return error(_("value %s for %s not in range [%"PRIuMAX",%"PRIuMAX"]"),
++ arg, optname(opt, flags), (uintmax_t)0, (uintmax_t)upper_bound);
++ if (errno)
++ return error_errno(_("value %s for %s cannot be parsed"),
++ arg, optname(opt, flags));
++
+ }
+
+ if (value > upper_bound)
-+ return error(_("value %"PRIuMAX" for %s exceeds %"PRIuMAX),
-+ value, optname(opt, flags), upper_bound);
++ return error(_("value %s for %s not in range [%"PRIuMAX",%"PRIuMAX"]"),
++ arg, optname(opt, flags), (uintmax_t)0, (uintmax_t)upper_bound);
+
+ switch (opt->precision) {
+ case 1:
-+ *(int8_t *)opt->value = value;
++ *(uint8_t *)opt->value = value;
+ return 0;
+ case 2:
-+ *(int16_t *)opt->value = value;
++ *(uint16_t *)opt->value = value;
+ return 0;
+ case 4:
-+ *(int32_t *)opt->value = value;
++ *(uint32_t *)opt->value = value;
+ return 0;
+ case 8:
-+ *(int64_t *)opt->value = value;
++ *(uint64_t *)opt->value = value;
+ return 0;
+ default:
+ BUG("invalid precision for option %s",
@@ t/t0040-parse-options.sh: cat >expect <<\EOF
m16: 0
timestamp: 0
@@ t/t0040-parse-options.sh: test_expect_success 'm16 limits range' '
- test_grep "value 65536 for option .m16. exceeds 65535" err
+ test_grep "value 65536 for option .m16. not in range \[0,65535\]" err
'
+test_expect_success 'u16 limits range' '
+ test-tool parse-options --u16 65535 >out &&
+ test_grep "u16: 65535" out &&
+ test_must_fail test-tool parse-options --u16 65536 2>err &&
-+ test_grep "value 65536 for option .u16. exceeds 65535" err
++ test_grep "value 65536 for option .u16. not in range \[0,65535\]" err
++'
++
++test_expect_success 'u16 does not accept negative value' '
++ test_must_fail test-tool parse-options --u16 -1 >out 2>err &&
++ test_grep "option .u16. does not accept negative values" err &&
++ test_must_be_empty out
+'
+
test_done
5: 20ab753baef = 6: 5b05451c346 parse-options: detect mismatches in integer signedness
-: ----------- > 7: 7115a7a31aa parse-options: introduce bounded integer options
---
base-commit: 5b97a56fa0e7d580dc8865b73107407c9b3f0eff
change-id: 20250401-b4-pks-parse-options-integers-9b4bbcf21011
next prev parent reply other threads:[~2025-04-16 10:02 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-01 15:01 [PATCH 0/5] parse-options: harden handling of integer values Patrick Steinhardt
2025-04-01 15:01 ` [PATCH 1/5] global: use designated initializers for options Patrick Steinhardt
2025-04-01 15:01 ` [PATCH 2/5] parse-options: introduce precision handling for `OPTION_INTEGER` Patrick Steinhardt
2025-04-01 18:47 ` René Scharfe
2025-04-15 10:26 ` Patrick Steinhardt
2025-04-01 15:01 ` [PATCH 3/5] parse-options: introduce precision handling for `OPTION_MAGNITUDE` Patrick Steinhardt
2025-04-01 15:01 ` [PATCH 4/5] parse-options: introduce `OPTION_UNSIGNED` Patrick Steinhardt
2025-04-01 15:01 ` [PATCH 5/5] parse-options: detect mismatches in integer signedness Patrick Steinhardt
2025-04-15 12:14 ` [PATCH v2 0/5] parse-options: harden handling of integer values Patrick Steinhardt
2025-04-15 12:14 ` [PATCH v2 1/5] global: use designated initializers for options Patrick Steinhardt
2025-04-15 12:14 ` [PATCH v2 2/5] parse-options: introduce precision handling for `OPTION_INTEGER` Patrick Steinhardt
2025-04-15 15:51 ` Phillip Wood
2025-04-16 10:28 ` Patrick Steinhardt
2025-04-15 16:59 ` Junio C Hamano
2025-04-16 10:28 ` Patrick Steinhardt
2025-04-15 12:14 ` [PATCH v2 3/5] parse-options: introduce precision handling for `OPTION_MAGNITUDE` Patrick Steinhardt
2025-04-15 12:14 ` [PATCH v2 4/5] parse-options: introduce `OPTION_UNSIGNED` Patrick Steinhardt
2025-04-15 15:52 ` Phillip Wood
2025-04-16 10:27 ` Patrick Steinhardt
2025-04-16 13:31 ` phillip.wood123
2025-04-15 17:38 ` René Scharfe
2025-04-16 10:28 ` Patrick Steinhardt
2025-04-15 12:14 ` [PATCH v2 5/5] parse-options: detect mismatches in integer signedness Patrick Steinhardt
2025-04-15 17:02 ` Junio C Hamano
2025-04-16 10:02 ` Patrick Steinhardt [this message]
2025-04-16 10:02 ` [PATCH v3 1/7] global: use designated initializers for options Patrick Steinhardt
2025-04-16 10:02 ` [PATCH v3 2/7] parse-options: check for overflow when parsing integers Patrick Steinhardt
2025-04-16 10:02 ` [PATCH v3 3/7] parse-options: introduce precision handling for `OPTION_INTEGER` Patrick Steinhardt
2025-04-16 17:29 ` Junio C Hamano
2025-04-16 10:02 ` [PATCH v3 4/7] parse-options: introduce precision handling for `OPTION_MAGNITUDE` Patrick Steinhardt
2025-04-16 10:02 ` [PATCH v3 5/7] parse-options: introduce `OPTION_UNSIGNED` Patrick Steinhardt
2025-04-16 18:50 ` Junio C Hamano
2025-04-17 8:15 ` Patrick Steinhardt
2025-04-16 10:02 ` [PATCH v3 6/7] parse-options: detect mismatches in integer signedness Patrick Steinhardt
2025-04-16 10:02 ` [PATCH v3 7/7] parse-options: introduce bounded integer options Patrick Steinhardt
2025-04-16 19:19 ` Junio C Hamano
2025-04-17 8:14 ` Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 0/7] parse-options: harden handling of integer values Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 1/7] parse: fix off-by-one for minimum signed values Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 2/7] global: use designated initializers for options Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 3/7] parse-options: support unit factors in `OPT_INTEGER()` Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 4/7] parse-options: rename `OPT_MAGNITUDE()` to `OPT_UNSIGNED()` Patrick Steinhardt
2025-04-17 15:17 ` Junio C Hamano
2025-04-17 10:49 ` [PATCH v4 5/7] parse-options: introduce precision handling for `OPTION_INTEGER` Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 6/7] parse-options: introduce precision handling for `OPTION_UNSIGNED` Patrick Steinhardt
2025-04-17 10:49 ` [PATCH v4 7/7] parse-options: detect mismatches in integer signedness Patrick Steinhardt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250416-b4-pks-parse-options-integers-v3-0-d390746bea79@pks.im \
--to=ps@pks.im \
--cc=git@vger.kernel.org \
--cc=glaubitz@physik.fu-berlin.de \
--cc=l.s.r@web.de \
--cc=peff@peff.net \
--cc=phillip.wood123@gmail.com \
--cc=stolee@gmail.com \
--cc=szeder.dev@gmail.com \
--cc=tmz@pobox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).