Float cast overflow in 'parseNsCookie'
Summary
The Netscape cookie parser reads the expiry field as a double and casts it to int64_t, then clamps the result to the time_t range. The clamp is one statement too late: if the parsed double is outside int64_t's range the cast itself is undefined, and what the clamp inspects is the product of that undefined conversion. Reproduces on master (9e7273583f83) via --load-cookies.
Details
/* src/NsCookieParser.cc:71-87, in the anonymous-namespace parseNsCookie */
71 int64_t expiryTime;
72 {
73 // chrome extension uses subsecond resolution for expiry time.
74 double expiryTimeDouble;
75 if (!util::parseDoubleNoThrow(expiryTimeDouble,
76 std::string(vs[4].first, vs[4].second))) {
77 return nullptr;
78 }
79 expiryTime = static_cast<int64_t>(expiryTimeDouble);
80 }
81 if (std::numeric_limits<time_t>::max() < expiryTime) {
82 expiryTime = std::numeric_limits<time_t>::max();
83 }
84 else if (std::numeric_limits<time_t>::min() > expiryTime) {
85 expiryTime = std::numeric_limits<time_t>::min();
86 }The clamp at :81-86 is correct in itself and clearly meant to handle out-of-range values. It just runs after :79. parseDoubleNoThrow accepts the full double range, so any magnitude above 2^63 makes the cast UB, and the clamp then reads whatever the implementation produced.
Reading the expiry as a double is deliberate. the comment at :73 explains that a Chrome extension writes sub second resolution. The bug is the ordering.
A fix belongs at :79: check before converting.
if (!(expiryTimeDouble >= static_cast<double>(std::numeric_limits<int64_t>::min()) &&
expiryTimeDouble <= static_cast<double>(std::numeric_limits<int64_t>::max()))) {
return nullptr;
}
expiryTime = static_cast<int64_t>(expiryTimeDouble);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 srcOne tab-separated line:
printf '.example.org\tTRUE\t/\tFALSE\t1e308\tpoc\tvalue\n' > c.txt
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 \
./src/aria2c --load-cookies=c.txt --dry-run=true --max-tries=1 --dir=/tmp/poc \
--no-conf=true --console-log-level=info http://127.0.0.1:1/xsrc/NsCookieParser.cc:79:39: runtime error: 1e+308 is outside the range of representable
values of type 'long'
#0 aria2::(anonymous namespace)::parseNsCookie(std::string const&, long)
src/NsCookieParser.cc:79:39
#1 aria2::NsCookieParser::parse(std::string const&, long) src/NsCookieParser.cc
#2 aria2::CookieStorage::load(std::string const&, long) src/CookieStorage.cc
#3 aria2::MultiUrlRequestInfo::prepare() src/MultiUrlRequestInfo.cc:220:35
[...]
SUMMARY: UndefinedBehaviorSanitizer: float-cast-overflow src/NsCookieParser.cc:79:39Impact
Undefined behaviour on a cookie file supplied to --load-cookies.
Source: aria2/aria2