Shift of a negative value in 'SocketCore::setIpDscp'
Summary
setIpDscp takes an int and shifts it left by two. --dscp is registered with a minimum of 0 and no maximum, so any value up to INT64_MAX is accepted; Option::getAsInt truncates it into an int that is usually negative by the time it arrives. Shifting a negative value left is undefined behaviour.
Details
/* src/SocketCore.h:142-148 */
142 // Set DSCP byte
143 void applyIpDscp();
144 static void setIpDscp(int ipDscp)
145 {
146 // Here we prepare DSCP value for IPTOS option, which sets whole DS field
147 ipDscp_ = ipDscp << 2;
148 }The registration:
/* src/OptionHandlerFactory.cc:666 */
666 OptionHandler* op(new NumberOptionHandler(PREF_DSCP, TEXT_DSCP, "0", 0));NumberOptionHandler(pref, desc, defaultValue, min, max, shortName) only min is given, so max keeps its default and no ceiling is enforced. Nothing between the option and the shift narrows the range to the six bits the field actually holds.
The call site is src/Context.cc:224:
224 SocketCore::setIpDscp(op->getAsInt(PREF_DSCP));getAsInt has already truncated strtol's long into int32_t by this point, which is filed separately. --dscp=2147483647 arrives intact and overflows on the shift; --dscp=2147483648 and larger arrive already negative and the shift itself is UB.
Two changes are needed and either one alone silences this line. Give the option its real range at OptionHandlerFactory.cc:666:
OptionHandler* op(new NumberOptionHandler(PREF_DSCP, TEXT_DSCP, "0", 0, 63));and stop getAsInt from narrowing silently.
PoC
autoreconf -i
mkdir build-asan && cd build-asan
CC=clang CXX=clang++ \
CPPFLAGS="-fsanitize=address,undefined -fsanitize=vptr -fno-omit-frame-pointer -g3 -O1" \
LDFLAGS="-fsanitize=address,undefined -fsanitize=vptr" \
../configure --disable-nls --with-openssl --without-gnutls --with-libxml2 \
--with-sqlite3 --with-libz --without-libssh2 --without-libcares \
--enable-bittorrent --enable-metalink
make -j"$(nproc)" -C deps && make -j"$(nproc)" -C srcfor V in 2147483647 2147483648 9223372036854775807; do
UBSAN_OPTIONS=print_stacktrace=1 ./src/aria2c --dscp="$V" --dry-run=true \
--max-tries=1 --dir=/tmp/poc --no-conf=true http://127.0.0.1:1/x
done=== --dscp=2147483647
src/SocketCore.h:147:22: runtime error: left shift of 2147483647 by 2 places
cannot be represented in type 'int'
=== --dscp=2147483648
src/SocketCore.h:147:22: runtime error: left shift of negative value -2147483648
=== --dscp=9223372036854775807
src/SocketCore.h:147:22: runtime error: left shift of negative value -1
#0 aria2::SocketCore::setIpDscp(int) src/SocketCore.h:147:22
#1 aria2::Context::Context(...) src/Context.cc:224
[...]
SUMMARY: UndefinedBehaviorSanitizer: invalid-shift-base src/SocketCore.h:147:22Impact
Undefined behaviour reachable from local configuration or from an RPC client that can call changeGlobalOption. The resulting ipDscp_ is applied with setsockopt(IP_TOS), so effect is a garbage DS field rather than memory corruption.
Source: aria2/aria2