#2387·aria2

Signed 'char' passed to an 'unsigned char' parameter in 'GroupId::toNumericId'

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

Summary

The same defect as in GroupId::expandUnique, in the function that parses a full 16-digit GID. hex[i] is char; util::hexCharToUInt takes unsigned char; bytes at or above 0x80 change value at the call. The entry point here is --gid, which also arrives from a saved session file, so fixing expandUnique alone leaves this one open.

Details

/* src/GroupId.cc:106-120 */
106  int GroupId::toNumericId(a2_gid_t& n, const char* hex)
107  {
108    a2_gid_t p = 0;
109    size_t i;
110    for (i = 0; hex[i]; ++i) {
111      unsigned int c = util::hexCharToUInt(hex[i]);
112      if (c == 255) {
113        return ERR_INVALID;
114      }
115      p <<= 4;
116      p |= c;
117    }
/* src/util.h:249 */
249  unsigned int hexCharToUInt(unsigned char ch);

The reject at :112 does its job — a non-hex byte returns 255 and the function bails. so the converted value never reaches p. The conversion at :111 still happens first.

Reached through getGID in download_helper.cc:112, which is on the path for every request group created from the command line or from --input-file:

  aria2::GroupId::toNumericId(...)                     src/GroupId.cc:111
  aria2::(anonymous namespace)::getGID(...)            src/download_helper.cc:112
  aria2::(anonymous namespace)::createRequestGroup(...) src/download_helper.cc
  aria2::createRequestGroupForUri(...)

A fix belongs at :111, the same as for expandUnique:

cpp
      unsigned int c = util::hexCharToUInt(static_cast<unsigned char>(hex[i]));

An unsigned char overload of hexCharToUInt taking char would close both at once.

PoC

Requires the implicit-conversion checks, which -fsanitize=undefined does not include. Build as in the bittorrent::processRootDictionarycase (-fsanitize=implicit-unsigned-integer-truncation,implicit-signed-integer-truncation,implicit-integer-sign-change plus the ignorelist that silences bitfield.h, base64.h, the util.h byte helpers and deps/).

bash
GID=$(python3 -c 'import sys; sys.stdout.write(bytes([0xff,0xfe]+list(range(0x80,0x8e))).decode("latin-1"))')
UBSAN_OPTIONS=print_stacktrace=1 ./src/aria2c --gid="$GID" --dry-run=true \
  --max-tries=1 --dir=/tmp/poc --no-conf=true http://127.0.0.1:1/x
src/GroupId.cc:111:42: runtime error: implicit conversion from type 'char' of value -61
    (8-bit, signed) to type 'unsigned char' changed the value to 195 (8-bit, unsigned)
    #0 aria2::GroupId::toNumericId(unsigned long&, char const*)  src/GroupId.cc:111:42
    #1 aria2::(anonymous namespace)::getGID(...)                 src/download_helper.cc:112:9
    #2 aria2::(anonymous namespace)::createRequestGroup(...)     src/download_helper.cc
    #3 aria2::createRequestGroupForUri(...)
    [...]
SUMMARY: UndefinedBehaviorSanitizer: implicit-integer-sign-change src/GroupId.cc:111:42

Impact

Implementation-defined conversion at a call boundary, reachable from the command line and from a session file. Just Type correctness issue.