perf synthetic-events: Fix line synchronization, bounds, and truncation bugs in proc maps reader

Fix critical logic and boundary bugs in read_proc_maps_line() and caller.
Ensure any mid-line hex/dec/char parsing failure invokes io__drain_line()
safely, using a do-while loop to read and discard remaining characters
until a newline or EOF is reached. Clamp pathname extraction size to
account for trailing sample ID headers, use standard '//toolong' fallback
literal for over-length pathnames, emit timeout flags for truncated entries
securely via goto out;, and cast event buffer pointers to avoid
_FORTIFY_SOURCE array bounds aborts across synthesis handlers.

Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
This commit is contained in:
Ian Rogers 2026-07-21 16:52:51 -07:00 committed by Namhyung Kim
parent e46a9b8150
commit 505a498a37

View File

@ -179,7 +179,8 @@ static int perf_event__prepare_comm(union perf_event *event, pid_t pid, pid_t ti
size = strlen(event->comm.comm) + 1;
size = PERF_ALIGN(size, sizeof(u64));
memset(event->comm.comm + size, 0, machine->id_hdr_size);
memset((char *)event + offsetof(struct perf_record_comm, comm) + size,
0, machine->id_hdr_size);
event->comm.header.size = (sizeof(event->comm) -
(sizeof(event->comm.comm) - size) +
machine->id_hdr_size);
@ -291,6 +292,18 @@ static int perf_event__synthesize_fork(const struct perf_tool *tool,
return 0;
}
static void io__drain_line(struct io *io, int ch)
{
if (ch == '\n')
return;
if (ch == -2 && io->data > io->buf && io->data[-1] == '\n')
return;
do {
ch = io__get_char(io);
} while (ch >= 0 && ch != '\n');
}
static bool read_proc_maps_line(struct io *io, __u64 *start, __u64 *end,
u32 *prot, u32 *flags, __u64 *offset,
u32 *maj, u32 *min,
@ -299,69 +312,127 @@ static bool read_proc_maps_line(struct io *io, __u64 *start, __u64 *end,
{
__u64 temp;
int ch;
char *start_pathname = pathname;
size_t written = 0;
bool overflowed = false;
if (io__get_hex(io, start) != '-')
ch = io__get_hex(io, start);
if (ch != '-') {
if (!io->eof)
io__drain_line(io, ch);
return false;
if (io__get_hex(io, end) != ' ')
}
ch = io__get_hex(io, end);
if (ch != ' ') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
/* map protection and flags bits */
*prot = 0;
ch = io__get_char(io);
if (ch == 'r')
*prot |= PROT_READ;
else if (ch != '-')
else if (ch != '-') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
ch = io__get_char(io);
if (ch == 'w')
*prot |= PROT_WRITE;
else if (ch != '-')
else if (ch != '-') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
ch = io__get_char(io);
if (ch == 'x')
*prot |= PROT_EXEC;
else if (ch != '-')
else if (ch != '-') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
ch = io__get_char(io);
if (ch == 's')
*flags = MAP_SHARED;
else if (ch == 'p')
*flags = MAP_PRIVATE;
else
else {
if (!io->eof)
io__drain_line(io, ch);
return false;
if (io__get_char(io) != ' ')
}
ch = io__get_char(io);
if (ch != ' ') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
if (io__get_hex(io, offset) != ' ')
ch = io__get_hex(io, offset);
if (ch != ' ') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
if (io__get_hex(io, &temp) != ':')
ch = io__get_hex(io, &temp);
if (ch != ':') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
*maj = temp;
if (io__get_hex(io, &temp) != ' ')
ch = io__get_hex(io, &temp);
if (ch != ' ') {
if (!io->eof)
io__drain_line(io, ch);
return false;
}
*min = temp;
ch = io__get_dec(io, inode);
if (ch != ' ') {
*pathname = '\0';
return ch == '\n';
if (ch == '\n') {
pathname[0] = '\0';
return true;
}
if (!io->eof)
io__drain_line(io, ch);
return false;
}
do {
ch = io__get_char(io);
} while (ch == ' ');
while (true) {
if (ch < 0)
return false;
if (ch == '\0' || ch == '\n' ||
(pathname + 1 - start_pathname) >= pathname_size) {
*pathname = '\0';
return true;
if (ch < 0) {
if (overflowed) {
strlcpy(pathname, "//toolong", pathname_size);
return true;
}
pathname[written] = '\0';
return written > 0;
}
*pathname++ = ch;
if (ch == '\0' || ch == '\n')
break;
if (written < (size_t)pathname_size - 1)
pathname[written++] = (char)ch;
else
overflowed = true;
ch = io__get_char(io);
}
if (overflowed)
strlcpy(pathname, "//toolong", pathname_size);
else
pathname[written] = '\0';
return true;
}
static void perf_record_mmap2__read_build_id(struct perf_record_mmap2 *event,
@ -463,45 +534,53 @@ int perf_event__synthesize_mmap_events(const struct perf_tool *tool,
while (!io.eof) {
static const char anonstr[] = "//anon";
size_t size, aligned_size;
/* ensure null termination since stack will be reused. */
event->mmap2.filename[0] = '\0';
__u64 start, end, pgoff, ino;
u32 prot, flags, maj, min;
/* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
if (!read_proc_maps_line(&io,
&event->mmap2.start,
&event->mmap2.len,
&event->mmap2.prot,
&event->mmap2.flags,
&event->mmap2.pgoff,
&event->mmap2.maj,
&event->mmap2.min,
&event->mmap2.ino,
sizeof(event->mmap2.filename),
event->mmap2.filename))
/* Read directly into event->mmap2.filename, clamping for id_hdr_size! */
if (!read_proc_maps_line(&io, &start, &end,
&prot, &flags, &pgoff,
&maj, &min, &ino,
sizeof(event->mmap2.filename) - machine->id_hdr_size,
event->mmap2.filename)) {
if (io.eof)
break;
continue;
if ((rdclock() - t) > timeout) {
pr_warning("Reading %s/proc/%d/task/%d/maps time out. "
"You may want to increase "
"the time limit by --proc-map-timeout\n",
machine->root_dir, pid, pid);
truncation = true;
goto out;
}
event->mmap2.ino_generation = 0;
if (!strcmp(event->mmap2.filename, ""))
strcpy(event->mmap2.filename, anonstr);
if (hugetlbfs_mnt_len &&
!strncmp(event->mmap2.filename, hugetlbfs_mnt, hugetlbfs_mnt_len)) {
strcpy(event->mmap2.filename, anonstr);
flags |= MAP_HUGETLB;
}
size = strlen(event->mmap2.filename) + 1;
aligned_size = PERF_ALIGN(size, sizeof(u64));
event->mmap2.header.type = PERF_RECORD_MMAP2;
/*
* Just like the kernel, see __perf_event_mmap in kernel/perf_event.c
* Just like the kernel, see perf_misc_flags() in
* kernel/events/core.c
*/
if (machine__is_host(machine))
event->header.misc = PERF_RECORD_MISC_USER;
else
event->header.misc = PERF_RECORD_MISC_GUEST_USER;
if ((event->mmap2.prot & PROT_EXEC) == 0) {
if (!mmap_data || (event->mmap2.prot & PROT_READ) == 0)
if ((rdclock() - t) > timeout) {
pr_warning("Reading %s/proc/%d/task/%d/maps time out. You may want to increase the time limit by --proc-map-timeout\n",
machine->root_dir, pid, pid);
truncation = true;
goto out;
}
if ((prot & PROT_EXEC) == 0) {
if (!mmap_data || (prot & PROT_READ) == 0)
continue;
event->header.misc |= PERF_RECORD_MISC_MMAP_DATA;
@ -511,26 +590,26 @@ int perf_event__synthesize_mmap_events(const struct perf_tool *tool,
if (truncation)
event->header.misc |= PERF_RECORD_MISC_PROC_MAP_PARSE_TIMEOUT;
if (!strcmp(event->mmap2.filename, ""))
strcpy(event->mmap2.filename, anonstr);
event->mmap2.header.size =
offsetof(struct perf_record_mmap2, filename) +
aligned_size;
if (hugetlbfs_mnt_len &&
!strncmp(event->mmap2.filename, hugetlbfs_mnt,
hugetlbfs_mnt_len)) {
strcpy(event->mmap2.filename, anonstr);
event->mmap2.flags |= MAP_HUGETLB;
}
/* Zero the padding and ID header trailer safely! */
memset((char *)event + offsetof(struct perf_record_mmap2, filename) + size, 0,
(aligned_size - size) + machine->id_hdr_size);
size = strlen(event->mmap2.filename) + 1;
aligned_size = PERF_ALIGN(size, sizeof(u64));
event->mmap2.len -= event->mmap.start;
event->mmap2.header.size = (sizeof(event->mmap2) -
(sizeof(event->mmap2.filename) - aligned_size));
memset(event->mmap2.filename + size, 0, machine->id_hdr_size +
(aligned_size - size));
event->mmap2.header.size += machine->id_hdr_size;
event->mmap2.start = start;
event->mmap2.len = end - start;
event->mmap2.pgoff = pgoff;
event->mmap2.maj = maj;
event->mmap2.min = min;
event->mmap2.ino = ino;
event->mmap2.ino_generation = 0;
event->mmap2.pid = tgid;
event->mmap2.tid = pid;
event->mmap2.prot = prot;
event->mmap2.flags = flags;
if (!symbol_conf.no_buildid_mmap2)
perf_record_mmap2__read_build_id(&event->mmap2, machine, false);
@ -579,7 +658,8 @@ static int perf_event__synthesize_cgroup(const struct perf_tool *tool,
event->cgroup.id = handle.cgroup_id;
strncpy(event->cgroup.path, path + mount_len, path_len);
memset(event->cgroup.path + path_len, 0, machine->id_hdr_size);
memset((char *)event + offsetof(struct perf_record_cgroup, path) + path_len,
0, machine->id_hdr_size);
if (perf_tool__process_synth_event(tool, event, machine, process) < 0) {
pr_debug("process synth event failed\n");