Skip to content

conf: fix one-byte OOB read/write underflow in chomp() - #95

Merged
troglobit merged 1 commit into
troglobit:masterfrom
94xhn:fix/chomp-oob-underflow
Jul 14, 2026
Merged

conf: fix one-byte OOB read/write underflow in chomp()#95
troglobit merged 1 commit into
troglobit:masterfrom
94xhn:fix/chomp-oob-underflow

Conversation

@94xhn

@94xhn 94xhn commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Problem

chomp() in src/conf.c trims trailing newlines from a line read
from a .service config file:

p = str + strlen(str) - 1;
while (*p == '\n')
        *p-- = 0;

For a string consisting of only a newline (str == "\n", exactly
what fgets() produces for a blank line in a .service file), p
starts at str + 0. The loop body zeroes str[0], decrements p
to str - 1, and then unconditionally dereferences *p to test the
loop condition — a one-byte read before the start of the buffer. If
that stray byte also happens to be 0x0A the loop keeps walking
backward, turning the OOB read into an OOB write as well.

This is reachable via the normal parse() -> read_line() -> chomp()
path whenever a .service file contains a blank line between
directives, and conf.c isn't covered by the project's unit tests
or the asan CI job's TESTS= list.

Repro

Built with the exact flags the asan CI job uses
(-fsanitize=address,undefined -fno-sanitize-recover=undefined -g -O1),
and a .service file with a blank line:

type _http._tcp
port 80

txt version=1

Running mdnsd -n -l debug against it reports:

conf.c:67:16: runtime error: load of address 0x... with insufficient space for an object of type 'char'

Fix

Bound the pointer before dereferencing it, mirroring the tab
indentation used by the rest of the function (the affected line was
indented with spaces instead of a tab):

p = str + strlen(str) - 1;
while (p >= str && *p == '\n')
        *p-- = 0;

Rebuilding with the same UBSan/ASan flags and rerunning the same
repro produces zero sanitizer errors; the blank line is now handled
as a safe no-op and the following txt version=1 line still parses
and shows up correctly in the resulting TXT record.

chomp() trims trailing newlines from a line read from a .service
config file.  For a string consisting of only a newline (str ==
n, exactly what fgets() produces for a blank line), the pointer
p starts at str+0.  The loop body zeroes str[0], decrements p to
str-1, and then unconditionally dereferences *p to test the loop
condition -- a one-byte read before the start of the buffer.  If
that stray byte also happens to be 0x0A the loop keeps walking
backward, turning the OOB read into an OOB write as well.

Confirmed with the project's own asan CI flags
(-fsanitize=address,undefined -fno-sanitize-recover=undefined):
a .service file with a blank line between two directives makes
UBSan report 'load of address ... with insufficient space for an
object of type char' at this exact line, reachable via the normal
parse() -> read_line() -> chomp() path.

Bound the pointer before dereferencing it, mirroring the tab
indentation used by the rest of the function (the line was
previously indented with spaces instead of a tab).

Signed-off-by: 94xhn <87560781+94xhn@users.noreply.github.com>
@troglobit

Copy link
Copy Markdown
Owner

Ouch, stale copy of chomp() from libite! Thank you for the fix!

@troglobit
troglobit merged commit 78dd2b1 into troglobit:master Jul 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants