#2390·aria2

Float cast overflow in 'parseNsCookie'

Author: 2rr0r4o3Created Aug 24, 2026Updated Aug 24, 2026

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.

cpp
      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

bash
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 src

One tab-separated line:

bash
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/x
src/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:39

Impact

Undefined behaviour on a cookie file supplied to --load-cookies.