diff --git a/programs/zstdcli.c b/programs/zstdcli.c index 31b06d67be5..936ed2f645b 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -17,6 +17,7 @@ #include /* strcmp, strlen */ #include /* fprintf(), stdin, stdout, stderr */ #include /* assert */ +#include /* INT_MAX */ #include "fileio.h" /* stdinmark, stdoutmark, ZSTD_EXTENSION */ #ifndef ZSTD_NOBENCH @@ -413,9 +414,20 @@ static int readIntFromChar(const char** stringPtr) { sign = -1; } if (readU32FromCharChecked(stringPtr, &result)) { errorOut(errorMsg); } + if (result > (unsigned)INT_MAX) { errorOut(errorMsg); } return (int) result * sign; } +/*! readCLevelFromChar() : + * @return : compression level read from input in `char` format, as readU32FromChar(). + * A value above INT_MAX is returned as INT_MAX, so that it is clamped to the maximum level + * like any other level that is too high, instead of wrapping around to a negative level. + * Note : function will exit() program if digit sequence overflows */ +static int readCLevelFromChar(const char** stringPtr) { + unsigned const level = readU32FromChar(stringPtr); + return level > (unsigned)INT_MAX ? INT_MAX : (int)level; +} + /*! readSizeTFromCharChecked() : * @return 0 if success, and store the result in *value. * allows and interprets K, KB, KiB, M, MB, MiB, G, GB and GiB suffix. @@ -791,7 +803,8 @@ static int init_cLevel(void) { DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: numeric value too large \n", ENV_CLEVEL, env); return ZSTDCLI_CLEVEL_DEFAULT; } else if (*ptr == 0) { - return sign * (int)absLevel; + /* above INT_MAX, clamp like a level that is too high rather than wrap */ + return sign * (absLevel > (unsigned)INT_MAX ? INT_MAX : (int)absLevel); } } DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: not a valid integer value \n", ENV_CLEVEL, env); @@ -1207,7 +1220,7 @@ int main(int argCount, const char* argv[]) #ifndef ZSTD_NOCOMPRESS /* compression Level */ if ((*argument>='0') && (*argument<='9')) { - dictCLevel = cLevel = (int)readU32FromChar(&argument); + dictCLevel = cLevel = readCLevelFromChar(&argument); continue; } #endif @@ -1282,7 +1295,7 @@ int main(int argCount, const char* argv[]) case 'e': /* compression Level */ argument++; - cLevelLast = (int)readU32FromChar(&argument); + cLevelLast = readCLevelFromChar(&argument); break; /* Modify Nb Iterations (benchmark only) */ diff --git a/tests/cli-tests/compression/levels.sh b/tests/cli-tests/compression/levels.sh index b8230f2a346..eb5a66a55bb 100755 --- a/tests/cli-tests/compression/levels.sh +++ b/tests/cli-tests/compression/levels.sh @@ -45,17 +45,21 @@ zstd --fast=200000 file -c | zstd -t zstd -5000000000 -f file && die "Level too large, must fail" ||: zstd --fast=5000000000 -f file && die "Level too large, must fail" ||: +zstd -3000000000 file -o file-3g.zst -q +cmp file-19.zst file-3g.zst || die "Level 3000000000 is clamped to 19" # Test setting a level through the environment variable ZSTD_CLEVEL=-10 zstd file -o file-f10-env.zst -q ZSTD_CLEVEL=1 zstd file -o file-1-env.zst -q ZSTD_CLEVEL=+19 zstd file -o file-19-env.zst -q ZSTD_CLEVEL=+99 zstd file -o file-99-env.zst -q +ZSTD_CLEVEL=+3000000000 zstd file -o file-3g-env.zst -q cmp file-f10.zst file-f10-env.zst || die "Environment variable failed to set level" cmp file-1.zst file-1-env.zst || die "Environment variable failed to set level" cmp file-19.zst file-19-env.zst || die "Environment variable failed to set level" cmp file-99.zst file-99-env.zst || die "Environment variable failed to set level" +cmp file-19.zst file-3g-env.zst || die "Environment variable failed to set level" # Test invalid environment clevel is the default level zstd -f file -q diff --git a/tests/cli-tests/compression/levels.sh.stderr.exact b/tests/cli-tests/compression/levels.sh.stderr.exact index fd7c076d277..4d6c771bb91 100644 --- a/tests/cli-tests/compression/levels.sh.stderr.exact +++ b/tests/cli-tests/compression/levels.sh.stderr.exact @@ -43,17 +43,21 @@ zstd -5000000000 -f file && die "Level too large, must fail" ||: error: numeric value overflows 32-bit unsigned int zstd --fast=5000000000 -f file && die "Level too large, must fail" ||: error: numeric value overflows 32-bit unsigned int +zstd -3000000000 file -o file-3g.zst -q +cmp file-19.zst file-3g.zst || die "Level 3000000000 is clamped to 19" # Test setting a level through the environment variable ZSTD_CLEVEL=-10 zstd file -o file-f10-env.zst -q ZSTD_CLEVEL=1 zstd file -o file-1-env.zst -q ZSTD_CLEVEL=+19 zstd file -o file-19-env.zst -q ZSTD_CLEVEL=+99 zstd file -o file-99-env.zst -q +ZSTD_CLEVEL=+3000000000 zstd file -o file-3g-env.zst -q cmp file-f10.zst file-f10-env.zst || die "Environment variable failed to set level" cmp file-1.zst file-1-env.zst || die "Environment variable failed to set level" cmp file-19.zst file-19-env.zst || die "Environment variable failed to set level" cmp file-99.zst file-99-env.zst || die "Environment variable failed to set level" +cmp file-19.zst file-3g-env.zst || die "Environment variable failed to set level" # Test invalid environment clevel is the default level zstd -f file -q diff --git a/tests/playTests.sh b/tests/playTests.sh index 44bc82b5610..3cc8d7344ac 100755 --- a/tests/playTests.sh +++ b/tests/playTests.sh @@ -1772,6 +1772,7 @@ then datagen > tmp zstd --adapt= tmp && die "invalid compression parameter" zstd -f -vv --adapt=min=10,max=9 tmp && die "--adapt must fail on incoherent bounds" + zstd -f --adapt=max=4294967295 tmp && die "--adapt must reject a level that overflows int" println "\n===> rsyncable mode " roundTripTest -g10M " --rsyncable"