Skip to content

Commit 084d2ac

Browse files
lorenzo-stoakesKAGA-KOKO
authored andcommitted
selftests/perf_events: Add a mmap() correctness test
Exercise various mmap(), munmap() and mremap() invocations, which might cause a perf buffer mapping to be split or truncated. To avoid hard coding the perf event and having dependencies on architectures and configuration options, scan through event types in sysfs and try to open them. On success, try to mmap() and if that succeeds try to mmap() the AUX buffer. In case that no AUX buffer supporting event is found, only test the base buffer mapping. If no mappable event is found or permissions are not sufficient, skip the tests. Reserve a PROT_NONE region for both rb and aux tests to allow testing the case where mremap unmaps beyond the end of a mapped VMA to prevent it from unmapping unrelated mappings. Signed-off-by: Lorenzo Stoakes <[email protected]> Co-developed-by: Thomas Gleixner <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Reviewed-by: Lorenzo Stoakes <[email protected]>
1 parent b024d7b commit 084d2ac

File tree

3 files changed

+238
-1
lines changed

3 files changed

+238
-1
lines changed

tools/testing/selftests/perf_events/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
sigtrap_threads
33
remove_on_exec
44
watermark_signal
5+
mmap

tools/testing/selftests/perf_events/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
33
LDFLAGS += -lpthread
44

5-
TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal
5+
TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal mmap
66
include ../lib.mk
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#define _GNU_SOURCE
3+
4+
#include <dirent.h>
5+
#include <sched.h>
6+
#include <stdbool.h>
7+
#include <stdio.h>
8+
#include <unistd.h>
9+
10+
#include <sys/ioctl.h>
11+
#include <sys/mman.h>
12+
#include <sys/syscall.h>
13+
#include <sys/types.h>
14+
15+
#include <linux/perf_event.h>
16+
17+
#include "../kselftest_harness.h"
18+
19+
#define RB_SIZE 0x3000
20+
#define AUX_SIZE 0x10000
21+
#define AUX_OFFS 0x4000
22+
23+
#define HOLE_SIZE 0x1000
24+
25+
/* Reserve space for rb, aux with space for shrink-beyond-vma testing. */
26+
#define REGION_SIZE (2 * RB_SIZE + 2 * AUX_SIZE)
27+
#define REGION_AUX_OFFS (2 * RB_SIZE)
28+
29+
#define MAP_BASE 1
30+
#define MAP_AUX 2
31+
32+
#define EVENT_SRC_DIR "/sys/bus/event_source/devices"
33+
34+
FIXTURE(perf_mmap)
35+
{
36+
int fd;
37+
void *ptr;
38+
void *region;
39+
};
40+
41+
FIXTURE_VARIANT(perf_mmap)
42+
{
43+
bool aux;
44+
unsigned long ptr_size;
45+
};
46+
47+
FIXTURE_VARIANT_ADD(perf_mmap, rb)
48+
{
49+
.aux = false,
50+
.ptr_size = RB_SIZE,
51+
};
52+
53+
FIXTURE_VARIANT_ADD(perf_mmap, aux)
54+
{
55+
.aux = true,
56+
.ptr_size = AUX_SIZE,
57+
};
58+
59+
static bool read_event_type(struct dirent *dent, __u32 *type)
60+
{
61+
char typefn[512];
62+
FILE *fp;
63+
int res;
64+
65+
snprintf(typefn, sizeof(typefn), "%s/%s/type", EVENT_SRC_DIR, dent->d_name);
66+
fp = fopen(typefn, "r");
67+
if (!fp)
68+
return false;
69+
70+
res = fscanf(fp, "%u", type);
71+
fclose(fp);
72+
return res > 0;
73+
}
74+
75+
FIXTURE_SETUP(perf_mmap)
76+
{
77+
struct perf_event_attr attr = {
78+
.size = sizeof(attr),
79+
.disabled = 1,
80+
.exclude_kernel = 1,
81+
.exclude_hv = 1,
82+
};
83+
struct perf_event_attr attr_ok = {};
84+
unsigned int eacces = 0, map = 0;
85+
struct perf_event_mmap_page *rb;
86+
struct dirent *dent;
87+
void *aux, *region;
88+
DIR *dir;
89+
90+
self->ptr = NULL;
91+
92+
dir = opendir(EVENT_SRC_DIR);
93+
if (!dir)
94+
SKIP(return, "perf not available.");
95+
96+
region = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
97+
ASSERT_NE(region, MAP_FAILED);
98+
self->region = region;
99+
100+
// Try to find a suitable event on this system
101+
while ((dent = readdir(dir))) {
102+
int fd;
103+
104+
if (!read_event_type(dent, &attr.type))
105+
continue;
106+
107+
fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
108+
if (fd < 0) {
109+
if (errno == EACCES)
110+
eacces++;
111+
continue;
112+
}
113+
114+
// Check whether the event supports mmap()
115+
rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
116+
if (rb == MAP_FAILED) {
117+
close(fd);
118+
continue;
119+
}
120+
121+
if (!map) {
122+
// Save the event in case that no AUX capable event is found
123+
attr_ok = attr;
124+
map = MAP_BASE;
125+
}
126+
127+
if (!variant->aux)
128+
continue;
129+
130+
rb->aux_offset = AUX_OFFS;
131+
rb->aux_size = AUX_SIZE;
132+
133+
// Check whether it supports a AUX buffer
134+
aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
135+
MAP_SHARED | MAP_FIXED, fd, AUX_OFFS);
136+
if (aux == MAP_FAILED) {
137+
munmap(rb, RB_SIZE);
138+
close(fd);
139+
continue;
140+
}
141+
142+
attr_ok = attr;
143+
map = MAP_AUX;
144+
munmap(aux, AUX_SIZE);
145+
munmap(rb, RB_SIZE);
146+
close(fd);
147+
break;
148+
}
149+
closedir(dir);
150+
151+
if (!map) {
152+
if (!eacces)
153+
SKIP(return, "No mappable perf event found.");
154+
else
155+
SKIP(return, "No permissions for perf_event_open()");
156+
}
157+
158+
self->fd = syscall(SYS_perf_event_open, &attr_ok, 0, -1, -1, 0);
159+
ASSERT_NE(self->fd, -1);
160+
161+
rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, self->fd, 0);
162+
ASSERT_NE(rb, MAP_FAILED);
163+
164+
if (!variant->aux) {
165+
self->ptr = rb;
166+
return;
167+
}
168+
169+
if (map != MAP_AUX)
170+
SKIP(return, "No AUX event found.");
171+
172+
rb->aux_offset = AUX_OFFS;
173+
rb->aux_size = AUX_SIZE;
174+
aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
175+
MAP_SHARED | MAP_FIXED, self->fd, AUX_OFFS);
176+
ASSERT_NE(aux, MAP_FAILED);
177+
self->ptr = aux;
178+
}
179+
180+
FIXTURE_TEARDOWN(perf_mmap)
181+
{
182+
ASSERT_EQ(munmap(self->region, REGION_SIZE), 0);
183+
if (self->fd != -1)
184+
ASSERT_EQ(close(self->fd), 0);
185+
}
186+
187+
TEST_F(perf_mmap, remap)
188+
{
189+
void *tmp, *ptr = self->ptr;
190+
unsigned long size = variant->ptr_size;
191+
192+
// Test the invalid remaps
193+
ASSERT_EQ(mremap(ptr, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
194+
ASSERT_EQ(mremap(ptr + HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
195+
ASSERT_EQ(mremap(ptr + size - HOLE_SIZE, HOLE_SIZE, size, MREMAP_MAYMOVE), MAP_FAILED);
196+
// Shrink the end of the mapping such that we only unmap past end of the VMA,
197+
// which should succeed and poke a hole into the PROT_NONE region
198+
ASSERT_NE(mremap(ptr + size - HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
199+
200+
// Remap the whole buffer to a new address
201+
tmp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
202+
ASSERT_NE(tmp, MAP_FAILED);
203+
204+
// Try splitting offset 1 hole size into VMA, this should fail
205+
ASSERT_EQ(mremap(ptr + HOLE_SIZE, size - HOLE_SIZE, size - HOLE_SIZE,
206+
MREMAP_MAYMOVE | MREMAP_FIXED, tmp), MAP_FAILED);
207+
// Remapping the whole thing should succeed fine
208+
ptr = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tmp);
209+
ASSERT_EQ(ptr, tmp);
210+
ASSERT_EQ(munmap(tmp, size), 0);
211+
}
212+
213+
TEST_F(perf_mmap, unmap)
214+
{
215+
unsigned long size = variant->ptr_size;
216+
217+
// Try to poke holes into the mappings
218+
ASSERT_NE(munmap(self->ptr, HOLE_SIZE), 0);
219+
ASSERT_NE(munmap(self->ptr + HOLE_SIZE, HOLE_SIZE), 0);
220+
ASSERT_NE(munmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE), 0);
221+
}
222+
223+
TEST_F(perf_mmap, map)
224+
{
225+
unsigned long size = variant->ptr_size;
226+
227+
// Try to poke holes into the mappings by mapping anonymous memory over it
228+
ASSERT_EQ(mmap(self->ptr, HOLE_SIZE, PROT_READ | PROT_WRITE,
229+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
230+
ASSERT_EQ(mmap(self->ptr + HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
231+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
232+
ASSERT_EQ(mmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
233+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
234+
}
235+
236+
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)