From 893d031cc2d50783f40e71026ff4e46913729bae Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Fri, 12 Aug 2022 13:19:03 +0200 Subject: [PATCH] Tightened syntax checks to discover bad numerical arguments --- CHANGES | 4 ++ filan_main.c | 24 ++++++--- socat.c | 6 +-- sycls.c | 1 + sycls.h | 2 + sysutils.c | 42 ++++++++++++++++ sysutils.h | 3 ++ test.sh | 67 ++++++++++++++++++++++++- utils.c | 2 +- xioopts.c | 138 ++++++++++++++++++++++++++++++++++----------------- 10 files changed, 233 insertions(+), 56 deletions(-) diff --git a/CHANGES b/CHANGES index ede923c..0dceb3f 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,10 @@ Corrections: Fixed bad parser error message on "socat /tmp/x\"x/x -" + Tightened syntax checks to detect numerical arguments that are missing + or have trailing garbage. + Test: INTEGER_GARBAGE + Porting: OpenSSL, at least 1.1 on Ubuntu, crashed with SIGSEGV under certain conditions: client connection to server with certificate with empty diff --git a/filan_main.c b/filan_main.c index 3f6f7e6..d19e7b6 100644 --- a/filan_main.c +++ b/filan_main.c @@ -21,7 +21,7 @@ static void filan_usage(FILE *fd); int main(int argc, const char *argv[]) { - const char **arg1, *a; + const char **arg1, *a0, *a; const char *filename = NULL, *waittimetxt; unsigned int m = 0; /* first FD (default) */ unsigned int n = FD_SETSIZE; /* last excl. */ @@ -50,27 +50,39 @@ int main(int argc, const char *argv[]) { case 'S': style = arg1[0][1]; break; case 'r': filan_rawoutput = true; break; case 'i': if (arg1[0][2]) { - a = *arg1+2; + a = a0 = *arg1+2; } else { ++arg1, --argc; - if ((a = *arg1) == NULL) { + if ((a = a0 = *arg1) == NULL) { Error("option -i requires an argument"); filan_usage(stderr); exit(1); } } m = strtoul(a, (char **)&a, 0); + if (a == a0) { + Error1("not a numerical arg in \"-b %s\"", a0); + } + if (*a != '\0') { + Error1("trailing garbage in \"-b %s\"", a0); + } n = m; break; case 'n': if (arg1[0][2]) { - a = *arg1+2; + a = a0 = *arg1+2; } else { ++arg1, --argc; - if ((a = *arg1) == NULL) { + if ((a = a0 = *arg1) == NULL) { Error("option -n requires an argument"); filan_usage(stderr); exit(1); } } n = strtoul(a, (char **)&a, 0); + if (a == a0) { + Error1("not a numerical arg in \"-b %s\"", a0); + } + if (*a != '\0') { + Error1("trailing garbage in \"-b %s\"", a0); + } break; case 'f': if (arg1[0][2]) { filename = *arg1+2; @@ -135,7 +147,7 @@ int main(int argc, const char *argv[]) { else if (!strcmp(outfname,"stderr")) { fdout=stderr; } /* file descriptor */ else if (*outfname == '+') { - a = outfname+1; + a = outfname+1; fildes = strtoul(a, (char **)&a, 0); if ((fdout = fdopen(fildes, "w")) == NULL) { Error2("can't fdopen file descriptor %lu: %s\n", fildes, strerror(errno)); diff --git a/socat.c b/socat.c index 728fa88..85540d9 100644 --- a/socat.c +++ b/socat.c @@ -238,7 +238,7 @@ int main(int argc, const char *argv[]) { Exit(1); } } - socat_opts.bufsiz = strtoul(a, (char **)&a, 0); + socat_opts.bufsiz = Strtoul(a, (char **)&a, 0, "-b"); break; case 's': if (arg1[0][2]) { socat_opt_hint(stderr, arg1[0][1], arg1[0][2]); Exit(1); } diag_set_int('e', E_FATAL); break; @@ -251,7 +251,7 @@ int main(int argc, const char *argv[]) { Exit(1); } } - rto = strtod(a, (char **)&a); + rto = Strtod(a, (char **)&a, "-t"); socat_opts.closwait.tv_sec = rto; socat_opts.closwait.tv_usec = (rto-socat_opts.closwait.tv_sec) * 1000000; @@ -265,7 +265,7 @@ int main(int argc, const char *argv[]) { Exit(1); } } - rto = strtod(a, (char **)&a); + rto = Strtod(a, (char **)&a, "-T"); socat_opts.total_timeout.tv_sec = rto; socat_opts.total_timeout.tv_usec = (rto-socat_opts.total_timeout.tv_sec) * 1000000; diff --git a/sycls.c b/sycls.c index 249a3ae..7ecb447 100644 --- a/sycls.c +++ b/sycls.c @@ -1736,6 +1736,7 @@ void Unsetenv(const char *name) { } #endif + #if WITH_READLINE char *Readline(const char *prompt) { diff --git a/sycls.h b/sycls.h index 8b2fbc5..a9dc883 100644 --- a/sycls.h +++ b/sycls.h @@ -167,6 +167,8 @@ void Abort(void); int Mkstemp(char *template); int Setenv(const char *name, const char *value, int overwrite); void Unsetenv(const char *name); +#endif /* WITH_SYCLS */ +#if WITH_SYCLS char *Readline(const char *prompt); void Using_history(void); diff --git a/sysutils.c b/sysutils.c index db95840..7f15128 100644 --- a/sysutils.c +++ b/sysutils.c @@ -824,3 +824,45 @@ int xiosetenvushort(const char *varname, unsigned short value, int overwrite) { return xiosetenv(varname, envbuff, overwrite, NULL); # undef XIO_SHORTLEN } + + +unsigned long int Strtoul(const char *nptr, char **endptr, int base, const char *txt) { + unsigned long res; + + res = strtoul(nptr, endptr, base); + if (nptr == *endptr) { + Error1("parseopts(): missing numerical value of option \"%s\"", txt); + } + if (**endptr != '\0') { + Error1("parseopts(): trailing garbage in numerical arg of option \"%s\"", txt); + } + return res; +} + +#if HAVE_STRTOLL +long long int Strtoll(const char *nptr, char **endptr, int base, const char *txt) { + long long int res; + + res = strtoul(nptr, endptr, base); + if (nptr == *endptr) { + Error1("parseopts(): missing numerical value of option \"%s\"", txt); + } + if (**endptr != '\0') { + Error1("parseopts(): trailing garbage in numerical arg of option \"%s\"", txt); + } + return res; +} +#endif /* HAVE_STRTOLL */ + +double Strtod(const char *nptr, char **endptr, const char *txt) { + double res; + + res = strtod(nptr, endptr); + if (nptr == *endptr) { + Error1("parseopts(): missing numerical value of option \"%s\"", txt); + } + if (**endptr != '\0') { + Error1("parseopts(): trailing garbage in numerical arg of option \"%s\"", txt); + } + return res; +} diff --git a/sysutils.h b/sysutils.h index 48c24b5..c680873 100644 --- a/sysutils.h +++ b/sysutils.h @@ -107,5 +107,8 @@ extern int xiosetenvulong(const char *varname, unsigned long value, int overwrite); extern int xiosetenvushort(const char *varname, unsigned short value, int overwrite); +extern unsigned long int Strtoul(const char *nptr, char **endptr, int base, const char *txt); +extern long long int Strtoll(const char *nptr, char **endptr, int base, const char *txt); +extern double Strtod(const char *nptr, char **endptr, const char *txt); #endif /* !defined(__sysutils_h_included) */ diff --git a/test.sh b/test.sh index 5fee7c9..df7b8a0 100755 --- a/test.sh +++ b/test.sh @@ -13886,7 +13886,7 @@ N=$((N+1)) # Test if unbalanced quoting in Socat addresses is detected NAME=UNBALANCED_QUOTE case "$TESTS" in -*%$N%*|*%functions%*|*%bugs%*|*%$NAME%*) +*%$N%*|*%functions%*|*%syntax%*|*%bugs%*|*%$NAME%*) TEST="$NAME: Test fix of unbalanced quoting" # Invoke Socat with an address containing unbalanced quoting. If Socat prints # a "syntax error" message, the test succeeds @@ -13900,6 +13900,8 @@ printf "test $F_n $TEST... " $N $CMD0 >/dev/null 2>"${te}0" if grep -q "syntax error" "${te}0"; then $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0" >&2; fi + if [ "$debug" ]; then cat ${te} >&2; fi numOK=$((numOK+1)) else $PRINTF "$FAILED\n" @@ -15537,6 +15539,69 @@ esac PORT=$((PORT+1)) N=$((N+1)) +# Test if trailing garbage in integer type options gives error +NAME=MISSING_INTEGER +case "$TESTS" in +*%$N%*|*%functions%*|*%syntax%*|*%bugs%*|*%$NAME%*) +TEST="$NAME: Error on option that's missing integer value" +# Invoke Socat with pty and option ispeed=b19200. +# When socat terminates with error the test succeeded +if ! eval $NUMCOND; then :; else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts - PTY,ispeed=b19200" +printf "test $F_n $TEST... " $N +$CMD0 /dev/null 2>"${te}0" +if grep -q "missing numerical value" "${te}0"; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0" + cat "${te}0" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + +# Test if trailing garbage in integer type options gives error +NAME=INTEGER_GARBAGE +case "$TESTS" in +*%$N%*|*%functions%*|*%syntax%*|*%bugs%*|*%$NAME%*) +TEST="$NAME: Error on trailing garbabe" +# Invoke Socat with pty and option ispeed=b19200. +# When socat terminates with error the test succeeded +if ! eval $NUMCOND; then :; else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts - PTY,ispeed=19200B" +printf "test $F_n $TEST... " $N +$CMD0 /dev/null 2>"${te}0" +if grep -q "trailing garbage" "${te}0"; then + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0" >&2; fi + if [ "$debug" ]; then cat ${te} >&2; fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0" + cat "${te}0" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ################################################################################## diff --git a/utils.c b/utils.c index 6a61e5b..63f8a6b 100644 --- a/utils.c +++ b/utils.c @@ -160,7 +160,7 @@ char *xiosubstr(char *scratch, const char *str, size_t from, size_t len) { *scratch = '\0'; return scratch0; } - + /* since version 1.7.2.4 socat supports C-99 behaviour of snprintf but still can handle the old glibc case with -1 return on truncation. diff --git a/xioopts.c b/xioopts.c index ecd30d4..d0ecfae 100644 --- a/xioopts.c +++ b/xioopts.c @@ -1926,7 +1926,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, nestlex(a, &tokp, &len, endkey, hquotes, squotes, nests, true, true, false); if (parsres < 0) { - Error1("option too long: \"%s\"", *a); + Error1("option too long: \"%s\"", *a); return -1; } else if (parsres > 0) { Error1("syntax error in \"%s\"", *a); @@ -2006,9 +2006,9 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, if (assign) { unsigned long ul; char *rest; - ul = strtoul(token, &rest/*!*/, 0); + ul = Strtoul(token, &rest, 0, a0); if (ul > UCHAR_MAX) { - Error3("parseopts_table(%s): byte value exceeds limit (%lu vs. %u), using max", + Error3("parseopts(): option \"%s\": byte value exceeds limit (%lu vs. %u), using max", a0, ul, UCHAR_MAX); (*opts)[i].value.u_byte = UCHAR_MAX; } else { @@ -2026,7 +2026,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, case TYPE_INT: if (assign) { char *rest; - (*opts)[i].value.u_int = strtoul(token, &rest/*!*/, 0); + (*opts)[i].value.u_int = Strtoul(token, &rest, 0, a0); } else { (*opts)[i].value.u_int = 1; } @@ -2038,10 +2038,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, (*opts)[i].value.u_bool = 1; } else { char *rest; - (*opts)[i].value.u_bool = strtoul(token, &rest, 0); - if (rest && *rest) { - Error1("error in option \"%s\": \"0\" or \"1\" required", a0); - } + (*opts)[i].value.u_bool = Strtoul(token, &rest, 0, a0); } Info2("setting option \"%s\" to %d", ent->desc->defname, (*opts)[i].value.u_bool); @@ -2055,11 +2052,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, (*opts)[i].value.u_uint = 1; } else { char *rest; - ulongval = strtoul(token, &rest/*!*/, 0); - if (ulongval > UINT_MAX) { - Error3("parseopts_table(%s): unsigned int value exceeds limit (%lu vs. %u), using max", - a0, ulongval, UINT_MAX); - } + ulongval = Strtoul(token, &rest, 0, a0); (*opts)[i].value.u_uint = ulongval; } Info2("setting option \"%s\" to %u", ent->desc->defname, @@ -2074,11 +2067,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, (*opts)[i].value.u_ushort = 1; } else { char *rest; - ulongval = strtoul(token, &rest/*!*/, 0); - if (ulongval > USHRT_MAX) { - Error3("parseopts_table(%s): unsigned short value exceeds limit (%lu vs. %u), using max", - a0, ulongval, USHRT_MAX); - } + ulongval = Strtoul(token, &rest, 0, a0); (*opts)[i].value.u_ushort = ulongval; } Info2("setting option \"%s\" to %u", ent->desc->defname, @@ -2096,7 +2085,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, (*opts)[i].value.u_long = 1; } else { char *rest; - slongval = strtol(token, &rest, 0); + slongval = Strtoul(token, &rest, 0, a0); (*opts)[i].value.u_long = slongval; } Info2("setting option \"%s\" to %lu", ent->desc->defname, @@ -2111,7 +2100,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, (*opts)[i].value.u_ulong = 1; } else { char *rest; - ulongval = strtoul(token, &rest, 0); + ulongval = Strtoul(token, &rest, 0, a0); (*opts)[i].value.u_ulong = ulongval; } Info2("setting option \"%s\" to %lu", ent->desc->defname, @@ -2131,11 +2120,14 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, } else { char *rest; # if HAVE_STRTOLL - slonglongval = strtoll(token, &rest, 0); + slonglongval = Strtoll(token, &rest, 0, a0); # else /* in this case, input value range is limited */ - slonglongval = strtol(token, &rest, 0); + slonglongval = Strtol(token, &rest, 0, a0); # endif /* HAVE_STRTOLL */ + if (*rest != '\0') { + Error1("parseopts(): trailing garbage in numerical arg of option \"%s\"", a0); + } (*opts)[i].value.u_longlong = slonglongval; } Info2("setting option \"%s\" to %Lu", ent->desc->defname, @@ -2150,7 +2142,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, } if (isdigit((*token)&0xff)) { char *rest; - (*opts)[i].value.u_uidt = strtoul(token, &rest/*!*/, 0); + (*opts)[i].value.u_uidt = Strtoul(token, &rest, 0, a0); } else { struct passwd *pwd; if ((pwd = getpwnam(token)) == NULL) { @@ -2168,7 +2160,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, continue; } if (isdigit((token[0])&0xff)) { char *rest; - (*opts)[i].value.u_gidt = strtoul(token, &rest/*!*/, 0); + (*opts)[i].value.u_gidt = Strtoul(token, &rest, 0, a0); } else { struct group *grp; grp = getgrnam(token); @@ -2188,7 +2180,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, } { char *rest; - (*opts)[i].value.u_modet = strtoul(token, &rest/*!*/, 8); + (*opts)[i].value.u_modet = Strtoul(token, &rest, 8, a0); } Info2("setting option \"%s\" to %u", ent->desc->defname, (*opts)[i].value.u_modet); @@ -2229,7 +2221,8 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, continue; } else { double val; - val = strtod(token, NULL); + char *rest; + val = Strtod(token, &rest, a0); if (val == HUGE_VAL || val == -HUGE_VAL || val == 0.0 && errno == ERANGE) { Error2("strtod(\"%s\", NULL): %s", token, strerror(errno)); @@ -2248,7 +2241,8 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, continue; } else { double val; - val = strtod(token, NULL); + char *rest; + val = Strtod(token, &rest, a0); if (val == HUGE_VAL || val == -HUGE_VAL || val == 0.0 && errno == ERANGE) { Error2("strtod(\"%s\", NULL): %s", token, strerror(errno)); @@ -2270,7 +2264,7 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, (*opts)[i].value.u_linger.l_onoff = 1; { char *rest; - (*opts)[i].value.u_linger.l_linger = strtoul(token, &rest/*!*/, 0); + (*opts)[i].value.u_linger.l_linger = Strtoul(token, &rest, 0, a0); } Info3("setting option \"%s\" to {%d,%d}", ent->desc->defname, (*opts)[i].value.u_linger.l_onoff, @@ -2287,12 +2281,15 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, { char *rest; (*opts)[i].value.u_int = strtoul(token, &rest, 0); - if (*rest != ':') { - Error1("option \"%s\": 2 arguments required", + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 2 arguments required", ent->desc->defname); } ++rest; - (*opts)[i].value2.u_int = strtoul(rest, &rest, 0); + (*opts)[i].value2.u_int = Strtoul(rest, &rest, 0, a0); } Info3("setting option \"%s\" to %d:%d", ent->desc->defname, (*opts)[i].value.u_int, (*opts)[i].value2.u_int); @@ -2306,8 +2303,14 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, { char *rest; (*opts)[i].value.u_int = strtoul(token, &rest, 0); + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } if (*rest != ':') { - Error1("option \"%s\": 2 arguments required", + Error1("parseopts(): trailing garbage in numerical arg of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 2 arguments required", ent->desc->defname); } ++rest; @@ -2334,8 +2337,14 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, { char *rest; (*opts)[i].value.u_int = strtoul(token, &rest, 0); + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } if (*rest != ':') { - Error1("option \"%s\": 2 arguments required", + Error1("parseopts(): trailing garbage in numerical arg of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 2 arguments required", ent->desc->defname); } ++rest; @@ -2355,18 +2364,28 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, { char *rest; (*opts)[i].value.u_int = strtoul(token, &rest, 0); + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 3 arguments required", ent->desc->defname); + } if (*rest != ':') { - Error1("option \"%s\": 3 arguments required", - ent->desc->defname); + Error1("parseopts(): trailing garbage in 1st numerical arg of option \"%s\"", a0); } ++rest; (*opts)[i].value2.u_int = strtoul(rest, &rest, 0); + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 3 arguments required", ent->desc->defname); + } if (*rest != ':') { - Error1("option \"%s\": 3 arguments required", - ent->desc->defname); + Error1("parseopts(): trailing garbage in 2nd numerical arg of option \"%s\"", a0); } ++rest; - (*opts)[i].value3.u_int = strtoul(rest, &rest, 0); + (*opts)[i].value3.u_int = Strtoul(rest, &rest, 0, a0); } Info4("setting option \"%s\" to %d:%d:%d", ent->desc->defname, (*opts)[i].value.u_int, (*opts)[i].value2.u_int, (*opts)[i].value3.u_int); @@ -2381,16 +2400,28 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, { char *rest; (*opts)[i].value.u_int = strtoul(token, &rest, 0); - if (*rest != ':') { + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { Error1("option \"%s\": 3 arguments required", ent->desc->defname); } + if (*rest != ':') { + Error1("parseopts(): trailing garbage in 1st numerical arg of option \"%s\"", a0); + } ++rest; (*opts)[i].value2.u_int = strtoul(rest, &rest, 0); - if (*rest != ':') { + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { Error1("option \"%s\": 3 arguments required", ent->desc->defname); } + if (*rest != ':') { + Error1("parseopts(): trailing garbage in 2nd numerical arg of option \"%s\"", a0); + } ++rest; optlen = 0; if ((result = dalan(rest, optbuf, &optlen, sizeof(optbuf), 'i')) != 0) { @@ -2415,16 +2446,28 @@ int parseopts_table(const char **a, unsigned int groups, struct opt **opts, { char *rest; (*opts)[i].value.u_int = strtoul(token, &rest, 0); - if (*rest != ':') { - Error1("option \"%s\": 3 arguments required", + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 3 arguments required", ent->desc->defname); } + if (*rest != ':') { + Error1("parseopts(): trailing garbage in 1st numerical arg of option \"%s\"", a0); + } ++rest; (*opts)[i].value2.u_int = strtoul(rest, &rest, 0); - if (*rest != ':') { - Error1("option \"%s\": 3 arguments required", + if (token == rest) { + Error1("parseopts(): missing numerical value of option \"%s\"", a0); + } + if (*rest == '\0') { + Error1("parseopts(): option \"%s\": 3 arguments required", ent->desc->defname); } + if (*rest != ':') { + Error1("parseopts(): trailing garbage in 2nd numerical arg of option \"%s\"", a0); + } ++rest; if (((*opts)[i].value3.u_string = strdup(rest)) == NULL) { Error("out of memory"); return -1; @@ -2873,9 +2916,14 @@ int retropt_int(struct opt *opts, int optcode, int *result) { while (opt->desc != ODESC_END) { if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + char *rest; switch (opt->desc->type) { case TYPE_INT: *result = opt->value.u_int; break; - case TYPE_STRING: *result = strtol(opt->value.u_string, NULL, 0); + case TYPE_STRING: *result = strtol(opt->value.u_string, &rest, 0); + if (*rest != '\0') { + Error1("retropts: trailing garbage in numerical arg of option \"%s\"", + opt->desc->defname); + } break; default: Error2("cannot convert type %d of option %s to int", opt->desc->type, opt->desc->defname);