From a66cd72f7d14ef1939f1478633abb28d85953b41 Mon Sep 17 00:00:00 2001 From: Martijn Dekker Date: Tue, 23 Nov 2021 21:35:30 +0100 Subject: [PATCH] arith: implement range checking for enum types Within arithmetic expressions, enumeration values of variables of a type created with the 'enum' command translate to index numbers from 0 to the number of elements minus 1. However, there was no range checking on this in the arithmetic subsystem, allowing the assignment of out-of-range values that did not correspond to any enumeration value. Variables of an enum type are internally unsigned short integers (NV_UINT16), like those created with 'integer -su', except with an additional discipline function (ENUM_disc). src/cmd/ksh93/bltins/enum.c, src/cmd/ksh93/include/builtins.h: - To implement range checking, the arithmetic system needs access to the 'nelem' (number of elements) member of 'struct Enum'. This is only defined locally in enum.c. We could move that to name.h so arith.c can access it, but enum.c has code that supports compiling as standalone. So, instead, define a quick extern function, b_enum_elem(), that does the necessary type conversion and returns a type's number of elements. - Add --man documentation for the arithmetic subsystem behaviour for enum types. Tell the enuminfo() function, which dynamically inserts values into the documentation, how to process new \f tags 'lastv' (the last-defined value) and 'lastn' (the number of the last element). src/cmd/ksh93/sh/arith.c: arith(): - For NV_UINT16 variables with an ENUM_disc discipline, check the range using b_enum_elem() and error out if necessary. Resolves: https://github.com/ksh93/ksh/issues/335 --- NEWS | 8 ++++++++ src/cmd/ksh93/COMPATIBILITY | 3 +++ src/cmd/ksh93/bltins/enum.c | 33 ++++++++++++++++++++++++++++---- src/cmd/ksh93/include/builtins.h | 2 ++ src/cmd/ksh93/include/version.h | 2 +- src/cmd/ksh93/sh.1 | 14 ++++++++++++-- src/cmd/ksh93/sh/arith.c | 9 +++++++++ 7 files changed, 64 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 376413456..e1ce87deb 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,14 @@ For full details, see the git log at: https://github.com/ksh93/ksh Any uppercase BUG_* names are modernish shell bug IDs. +2021-11-23: + +- A bug was fixed that allowed arithmetic expressions to assign out-of-range + values to variables of an enumeration type defined with the 'enum' command, + causing undefined behavior. Within arithmetic expressions, enumeration + values translate to index numbers from 0 to the number of elements minus 1. + That range is now checked for. Decimal fractions are ignored. + 2021-11-21: - It is now possible to use types defined by 'enum' in contexts where the diff --git a/src/cmd/ksh93/COMPATIBILITY b/src/cmd/ksh93/COMPATIBILITY index 87eeb5e52..670d548a5 100644 --- a/src/cmd/ksh93/COMPATIBILITY +++ b/src/cmd/ksh93/COMPATIBILITY @@ -161,6 +161,9 @@ For more details, see the NEWS file and for complete details, see the git log. and $((...)). Setting the --posix compliance option turns on the recognition of the leading octal zero for all arithmetic contexts. +29. It is now an error for arithmetic expressions to assign an out-of-range + index value to a variable of an enumeration type created with 'enum'. + ____________________________________________________________________________ KSH-93 VS. KSH-88 diff --git a/src/cmd/ksh93/bltins/enum.c b/src/cmd/ksh93/bltins/enum.c index 3130b189c..782ecfd51 100644 --- a/src/cmd/ksh93/bltins/enum.c +++ b/src/cmd/ksh93/bltins/enum.c @@ -21,8 +21,10 @@ #pragma prototyped #include "defs.h" +#define ENUM_ID "enum (ksh 93u+m) 2021-11-23" + const char sh_optenum[] = -"[-?@(#)$Id: enum (ksh 93u+m) 2021-11-21 $\n]" +"[-?@(#)$Id: " ENUM_ID " $\n]" "[--catalog?" ERROR_CATALOG "]" "[+NAME?enum - create an enumeration type]" "[+DESCRIPTION?\benum\b is a declaration command that creates an enumeration " @@ -30,6 +32,7 @@ const char sh_optenum[] = "array variable \atypename\a.]" "[+?If the list of \avalue\as is omitted, then \atypename\a must name an " "indexed array variable with at least two elements.]" +"[+?For more information, see \atypename\a \b--man\b.]" "[i:ignorecase?The values are case insensitive.]" "\n" "\n\atypename\a[\b=(\b \avalue\a ... \b)\b]\n" @@ -43,15 +46,19 @@ const char sh_optenum[] = ; static const char enum_type[] = -"[-1c?\n@(#)$Id: type (ksh 93u+m) 2021-11-21 $\n]" +"[-?@(#)$Id: " ENUM_ID " $\n]" "[--catalog?" ERROR_CATALOG "]" "[+NAME?\f?\f - create an instance of type \b\f?\f\b]" "[+DESCRIPTION?The \b\f?\f\b declaration command creates a variable for " "each \aname\a with enumeration type \b\f?\f\b, a type that has been " "created with the \benum\b(1) command.]" "[+?The variable can have one of the following values: \fvalues\f. " - "The values are \fcase\fcase sensitive.]" -"[+?If \b=\b\avalue\a is omitted, the default is \fdefault\f.]" + "The values are \fcase\fcase sensitive. " + "If \b=\b\avalue\a is omitted, the default is \fdefault\f.]" +"[+?Within arithmetic expressions, these values translate to index numbers " + "from \b0\b (for \fdefault\f) to \flastn\f (for \flastv\f). " + "It is an error for an arithmetic expression to assign a value " + "outside of that range. Decimal fractions are ignored.]" "[+?If no \aname\as are specified then the names and values of all " "variables of this type are written to standard output.]" "[+?\b\f?\f\b is built in to the shell as a declaration command so that " @@ -90,6 +97,14 @@ struct Enum const char *values[1]; }; +/* + * For range checking in arith.c + */ +short b_enum_nelem(Namfun_t *fp) +{ + return(((struct Enum *)fp)->nelem); +} + static int enuminfo(Opt_t* op, Sfio_t *out, const char *str, Optdisc_t *fp) { Namval_t *np; @@ -102,6 +117,16 @@ static int enuminfo(Opt_t* op, Sfio_t *out, const char *str, Optdisc_t *fp) return(0); if(strcmp(str,"default")==0) sfprintf(out,"\b%s\b",ep->values[0]); + else if(memcmp(str,"last",4)==0) + { + while(ep->values[++n]) + ; + n--; + if(str[4]=='v') + sfprintf(out,"\b%s\b",ep->values[n]); + else + sfprintf(out,"\b%d\b",n); + } else if(strcmp(str,"case")==0) { if(ep->iflag) diff --git a/src/cmd/ksh93/include/builtins.h b/src/cmd/ksh93/include/builtins.h index 36bce3e29..23c2eebd5 100644 --- a/src/cmd/ksh93/include/builtins.h +++ b/src/cmd/ksh93/include/builtins.h @@ -124,6 +124,8 @@ extern int b_times(int, char*[],Shbltin_t*); extern int B_echo(int, char*[],Shbltin_t*); #endif /* SHOPT_ECHOPRINT */ +extern short b_enum_nelem(Namfun_t*); + #undef extern extern const char e_alrm1[]; diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index 531a571d4..010a2bd13 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -21,7 +21,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2021-11-21" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2021-11-23" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index 370ed2f3d..2af057df1 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -6012,8 +6012,8 @@ See for usage and description. .TP \(dd \f3enum\fP \*(OK \f3\-i\fP \*(CK \f2type\^\fP\*(OK=(\f2value\^\fP .\|.\|.) \*(CK -Creates a declaration command named \f2type\^\fP that is an -integer type that allows one of the specified \f2value\fPs as +Creates a declaration command named \f2type\^\fP that +allows one of the specified \f2value\fPs as enumeration names. If \f3=(\fP\f2value\^\ .\|.\|.\|\fP\f3)\fP is omitted, then \f2type\^\fP must be an indexed array variable with at least two elements and the values are taken from this array variable. @@ -6022,6 +6022,16 @@ If is specified the values are case-insensitive. Declaration commands are created as special builtins that cannot be removed or overridden by shell functions. +Each created declaration command has a \f3--man\fP option that +shows documentation on its type's behavior and possible values. +.RS +.PP +Within arithmetic expressions (see +.I "Arithmetic Evaluation" +above), enumeration type values translate to index numbers between 0 and the +number of defined values minus 1. It is an error for an arithmetic expression +to assign a value outside of that range. Decimal fractions are ignored. +.RE .TP \(dg \f3eval\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK The arguments are read as input diff --git a/src/cmd/ksh93/sh/arith.c b/src/cmd/ksh93/sh/arith.c index 3f76b06ed..0cc88b674 100644 --- a/src/cmd/ksh93/sh/arith.c +++ b/src/cmd/ksh93/sh/arith.c @@ -243,6 +243,15 @@ static Sfdouble_t arith(const char **ptr, struct lval *lvalue, int type, Sfdoubl np = (Namval_t*)lvalue->value; np = scope(np, lvalue, 1); } + if(nv_isattr(np,NV_UINT16)==NV_UINT16) + { + Namfun_t *fp = nv_hasdisc(np, &ENUM_disc); + if(fp && (n < 0.0 || n > (Sfdouble_t)(b_enum_nelem(fp) - 1))) + { + errormsg(SH_DICT, ERROR_exit(1), "%s: value %ld out of enum range", nv_name(np), (long)n); + UNREACHABLE(); + } + } nv_putval(np, (char*)&n, NV_LDOUBLE); if(lvalue->eflag) lvalue->ptr = (void*)nv_hasdisc(np,&ENUM_disc);