The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=2e0caa7c7e14d7bdc89ec43be9bc848abe1ca264
commit 2e0caa7c7e14d7bdc89ec43be9bc848abe1ca264 Author: Dag-Erling Smørgrav <d...@freebsd.org> AuthorDate: 2025-08-02 14:05:36 +0000 Commit: Dag-Erling Smørgrav <d...@freebsd.org> CommitDate: 2025-08-02 14:05:36 +0000 libutil: Really fix expand_number(3) It is unclear whether this function was originally intended to support negative numbers. The original implementation used signed integers, but actually giving it a negative number as input would have invoked undefined behavior, and the comments (since removed) only mentioned positive numbers. Fifteen years ago, I “fixed” this by changing the type from signed to unsigned. However, it would still have accepted an input with a leading minus sign (though it would have returned its absolute value). Fifteen years on, change the type back to signed and fix the logic so it correctly handles both positive and negative numbers without invoking undefined behavior. This makes it a better match for humanize_number(3), which it is supposed to complement. Fixes: bbb2703b4f46 Reviewed by: jhb Differential Revision: https://reviews.freebsd.org/D51542 --- lib/libutil/expand_number.3 | 56 +++++++++++++++++++------------ lib/libutil/expand_number.c | 82 ++++++++++++++++++++++++++++++++++----------- lib/libutil/libutil.h | 2 +- usr.sbin/ctld/conf.cc | 12 ++++--- usr.sbin/ctld/parse.y | 26 +++++++------- 5 files changed, 118 insertions(+), 60 deletions(-) diff --git a/lib/libutil/expand_number.3 b/lib/libutil/expand_number.3 index 3609b1ad3939..1b932400de69 100644 --- a/lib/libutil/expand_number.3 +++ b/lib/libutil/expand_number.3 @@ -1,5 +1,6 @@ .\" Copyright (c) 2007 Eric Anderson <ander...@freebsd.org> .\" Copyright (c) 2007 Pawel Jakub Dawidek <p...@freebsd.org> +.\" Copyright (c) 2025 Dag-Erling Smørgrav <d...@freebsd.org> .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -23,43 +24,49 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 13, 2023 +.Dd July 25, 2025 .Dt EXPAND_NUMBER 3 .Os .Sh NAME .Nm expand_number -.Nd format a number from human readable form +.Nd parse a number from human readable form .Sh LIBRARY .Lb libutil .Sh SYNOPSIS .In libutil.h .Ft int .Fo expand_number -.Fa "const char *buf" "uint64_t *num" +.Fa "const char *buf" "int64_t *num" .Fc .Sh DESCRIPTION The .Fn expand_number -function parses the +function parses the number in the string pointed to by its .Fa buf -string and stores a unsigned 64-bit quantity at -.Fa *num . +argument and stores the number it represents as a signed 64-bit +quantity in the location pointed to by its +.Fa *num +argument. .Pp -The -.Fn expand_number -function -is case-insensitive and -follows the SI power of two convention. +The input string must consist of a decimal number, optionally preceded +by a +.Sq + +or +.Sq - +sign, and optionally followed, without intervening whitespace, by a +suffix indicating a power-of-two multiplier to apply. +Any amount of whitespace at the beginning of the string will be +ignored. .Pp -The suffixes are: +Recognized suffixes are: .Bl -column "Suffix" "Description" "1000000000000000000" -offset indent .It Sy "Suffix" Ta Sy "Description" Ta Sy "Multiplier" -.It Li K Ta No kilo Ta 1024 -.It Li M Ta No mega Ta 1048576 -.It Li G Ta No giga Ta 1073741824 -.It Li T Ta No tera Ta 1099511627776 -.It Li P Ta No peta Ta 1125899906842624 -.It Li E Ta No exa Ta 1152921504606846976 +.It Li K Ta No kilo Ta 1,024 +.It Li M Ta No mega Ta 1,048,576 +.It Li G Ta No giga Ta 1,073,741,824 +.It Li T Ta No tera Ta 1,099,511,627,776 +.It Li P Ta No peta Ta 1,125,899,906,842,624 +.It Li E Ta No exa Ta 1,152,921,504,606,846,976 .El .Pp For historical reasons, the @@ -68,7 +75,11 @@ function accepts and ignores a single .Dq B suffix at the end of the .Fa buf -string. +string (i.e. +.Dq 5b +is interpreted as 5, and +.Dq 5kb +is interpreted as 5,120). However, the usage of this suffix is discouraged. .Sh RETURN VALUES .Rv -std @@ -78,11 +89,12 @@ The function will fail if: .Bl -tag -width Er .It Bq Er EINVAL -The given string contains no digits. +The given string does not contain a valid number. .It Bq Er EINVAL -An unrecognized suffix was given. +An unrecognized suffix was encountered. .It Bq Er ERANGE -Result doesn't fit into 64 bits. +The given string represents a number which does not fit into a +.Vt int64_t . .El .Sh SEE ALSO .Xr humanize_number 3 diff --git a/lib/libutil/expand_number.c b/lib/libutil/expand_number.c index fc2ea8e8b17c..f4c19d7867a3 100644 --- a/lib/libutil/expand_number.c +++ b/lib/libutil/expand_number.c @@ -3,6 +3,7 @@ * * Copyright (c) 2007 Eric Anderson <ander...@freebsd.org> * Copyright (c) 2007 Pawel Jakub Dawidek <p...@freebsd.org> + * Copyright (c) 2025 Dag-Erling Smørgrav <d...@freebsd.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,79 +29,120 @@ */ #include <sys/types.h> + #include <ctype.h> #include <errno.h> #include <inttypes.h> #include <libutil.h> +#include <stdbool.h> #include <stdint.h> int -expand_number(const char *buf, uint64_t *num) +expand_number(const char *buf, int64_t *num) { char *endptr; - uintmax_t umaxval; - uint64_t number; - unsigned shift; + uintmax_t number; + unsigned int shift; + bool neg; int serrno; + /* + * Skip whitespace and optional sign. + */ + while (isspace((unsigned char)*buf)) + buf++; + if (*buf == '-') { + neg = true; + buf++; + } else { + neg = false; + if (*buf == '+') + buf++; + } + + /* + * The next character should be the first digit of the number. If + * we don't enforce this ourselves, strtoumax() will allow further + * whitespace and a (second?) sign. + */ + if (!isdigit((unsigned char)*buf)) { + errno = EINVAL; + return (-1); + } + serrno = errno; errno = 0; - umaxval = strtoumax(buf, &endptr, 0); - if (umaxval > UINT64_MAX) - errno = ERANGE; + number = strtoumax(buf, &endptr, 0); if (errno != 0) return (-1); errno = serrno; - number = umaxval; switch (tolower((unsigned char)*endptr)) { case 'e': shift = 60; + endptr++; break; case 'p': shift = 50; + endptr++; break; case 't': shift = 40; + endptr++; break; case 'g': shift = 30; + endptr++; break; case 'm': shift = 20; + endptr++; break; case 'k': shift = 10; + endptr++; break; - case 'b': - shift = 0; - break; - case '\0': /* No unit. */ - *num = number; - return (0); default: - /* Unrecognized unit. */ - errno = EINVAL; - return (-1); + shift = 0; } /* * Treat 'b' as an ignored suffix for all unit except 'b', * otherwise there should be no remaining character(s). */ - endptr++; - if (shift != 0 && tolower((unsigned char)*endptr) == 'b') + if (tolower((unsigned char)*endptr) == 'b') endptr++; if (*endptr != '\0') { errno = EINVAL; return (-1); } + /* + * Apply the shift and check for overflow. + */ if ((number << shift) >> shift != number) { /* Overflow */ errno = ERANGE; return (-1); } - *num = number << shift; + number <<= shift; + + /* + * Apply the sign and check for overflow. + */ + if (neg) { + if (number > 0x8000000000000000LLU /* -INT64_MIN */) { + errno = ERANGE; + return (-1); + } + *num = -number; + } else { + if (number > INT64_MAX) { + errno = ERANGE; + return (-1); + } + *num = number; + } + return (0); } diff --git a/lib/libutil/libutil.h b/lib/libutil/libutil.h index 7d8bfdf67fac..6d36a0c291c6 100644 --- a/lib/libutil/libutil.h +++ b/lib/libutil/libutil.h @@ -89,7 +89,7 @@ __BEGIN_DECLS char *auth_getval(const char *_name); void clean_environment(const char * const *_white, const char * const *_more_white); -int expand_number(const char *_buf, uint64_t *_num); +int expand_number(const char *_buf, int64_t *_num); int extattr_namespace_to_string(int _attrnamespace, char **_string); int extattr_string_to_namespace(const char *_string, int *_attrnamespace); int flopen(const char *_path, int _flags, ...); diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc index e86b44ee5004..f3285ebf9d56 100644 --- a/usr.sbin/ctld/conf.cc +++ b/usr.sbin/ctld/conf.cc @@ -409,7 +409,8 @@ lun_set_blocksize(size_t value) bool lun_set_device_type(const char *value) { - uint64_t device_type; + const char *errstr; + int device_type; if (strcasecmp(value, "disk") == 0 || strcasecmp(value, "direct") == 0) @@ -421,9 +422,12 @@ lun_set_device_type(const char *value) strcasecmp(value, "dvd") == 0 || strcasecmp(value, "dvdrom") == 0) device_type = T_CDROM; - else if (expand_number(value, &device_type) != 0 || device_type > 15) { - log_warnx("invalid device-type \"%s\" for lun \"%s\"", value, - lun->l_name); + else { + device_type = strtonum(value, 0, 15, &errstr); + if (errstr != NULL) { + log_warnx("invalid device-type \"%s\" for lun \"%s\"", value, + lun->l_name); + } return (false); } diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y index 56455d33bd27..c0146262e895 100644 --- a/usr.sbin/ctld/parse.y +++ b/usr.sbin/ctld/parse.y @@ -105,7 +105,7 @@ statement: debug: DEBUG STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -120,7 +120,7 @@ debug: DEBUG STR timeout: TIMEOUT STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -135,7 +135,7 @@ timeout: TIMEOUT STR maxproc: MAXPROC STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -172,7 +172,7 @@ isns_server: ISNS_SERVER STR isns_period: ISNS_PERIOD STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -187,7 +187,7 @@ isns_period: ISNS_PERIOD STR isns_timeout: ISNS_TIMEOUT STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -432,7 +432,7 @@ portal_group_redirect: REDIRECT STR portal_group_tag: TAG STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -448,7 +448,7 @@ portal_group_tag: TAG STR portal_group_dscp : DSCP STR { - uint64_t tmp; + int64_t tmp; if (strcmp($2, "0x") == 0) { tmp = strtol($2 + 2, NULL, 16); @@ -488,7 +488,7 @@ portal_group_dscp portal_group_pcp: PCP STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -704,7 +704,7 @@ target_lun: LUN lun_number lun_number: STR { - uint64_t tmp; + int64_t tmp; if (expand_number($1, &tmp) != 0) { yyerror("invalid numeric value"); @@ -720,7 +720,7 @@ lun_number: STR target_lun_ref: LUN STR STR { - uint64_t tmp; + int64_t tmp; bool ok; if (expand_number($2, &tmp) != 0) { @@ -778,7 +778,7 @@ lun_backend: BACKEND STR lun_blocksize: BLOCKSIZE STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -816,7 +816,7 @@ lun_device_type: DEVICE_TYPE STR lun_ctl_lun: CTL_LUN STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -866,7 +866,7 @@ lun_serial: SERIAL STR lun_size: SIZE STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value");