Skip to content

Commit 0ace8f2

Browse files
ljskernelakpm00
authored andcommitted
tools/testing/selftests: add tests for !tgt, src mremap() merges
Test that mremap()'ing a VMA into a position such that the target VMA on merge is unfaulted and the source faulted is correctly performed. We cover 4 cases: 1. Previous VMA unfaulted: copied -----| v |-----------|.............| | unfaulted |(faulted VMA)| |-----------|.............| prev target = prev, expand prev to cover. 2. Next VMA unfaulted: copied -----| v |.............|-----------| |(faulted VMA)| unfaulted | |.............|-----------| next target = next, expand next to cover. 3. Both adjacent VMAs unfaulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| unfaulted | |-----------|.............|-----------| prev next target = prev, expand prev to cover. 4. prev unfaulted, next faulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| faulted | |-----------|.............|-----------| prev next target = prev, expand prev to cover. Essentially equivalent to 3, but with additional requirement that next's anon_vma is the same as the copied VMA's. Each of these are performed with MREMAP_DONTUNMAP set, which will cause a KASAN assert for UAF or an assert on zero refcount anon_vma if a bug exists with correctly propagating anon_vma state in each scenario. Link: https://lkml.kernel.org/r/f903af2930c7c2c6e0948c886b58d0f42d8e8ba3.1767638272.git.lorenzo.stoakes@oracle.com Fixes: 879bca0 ("mm/vma: fix incorrectly disallowed anonymous VMA merges") Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com> Cc: David Hildenbrand (Red Hat) <david@kernel.org> Cc: Jann Horn <jannh@google.com> Cc: Jeongjun Park <aha310510@gmail.com> Cc: Liam Howlett <liam.howlett@oracle.com> Cc: Pedro Falcato <pfalcato@suse.de> Cc: Rik van Riel <riel@surriel.com> Cc: Vlastimil Babka <vbabka@suse.cz> Cc: Yeoreum Yun <yeoreum.yun@arm.com> Cc: Harry Yoo <harry.yoo@oracle.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent 61f67c2 commit 0ace8f2

1 file changed

Lines changed: 232 additions & 0 deletions

File tree

tools/testing/selftests/mm/merge.c

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,4 +1171,236 @@ TEST_F(merge, mremap_correct_placed_faulted)
11711171
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size);
11721172
}
11731173

1174+
TEST_F(merge, mremap_faulted_to_unfaulted_prev)
1175+
{
1176+
struct procmap_fd *procmap = &self->procmap;
1177+
unsigned int page_size = self->page_size;
1178+
char *ptr_a, *ptr_b;
1179+
1180+
/*
1181+
* mremap() such that A and B merge:
1182+
*
1183+
* |------------|
1184+
* | \ |
1185+
* |-----------| | / |---------|
1186+
* | unfaulted | v \ | faulted |
1187+
* |-----------| / |---------|
1188+
* B \ A
1189+
*/
1190+
1191+
/* Map VMA A into place. */
1192+
ptr_a = mmap(&self->carveout[page_size + 3 * page_size],
1193+
3 * page_size,
1194+
PROT_READ | PROT_WRITE,
1195+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1196+
ASSERT_NE(ptr_a, MAP_FAILED);
1197+
/* Fault it in. */
1198+
ptr_a[0] = 'x';
1199+
1200+
/*
1201+
* Now move it out of the way so we can place VMA B in position,
1202+
* unfaulted.
1203+
*/
1204+
ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size,
1205+
MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]);
1206+
ASSERT_NE(ptr_a, MAP_FAILED);
1207+
1208+
/* Map VMA B into place. */
1209+
ptr_b = mmap(&self->carveout[page_size], 3 * page_size,
1210+
PROT_READ | PROT_WRITE,
1211+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1212+
ASSERT_NE(ptr_b, MAP_FAILED);
1213+
1214+
/*
1215+
* Now move VMA A into position with MREMAP_DONTUNMAP to catch incorrect
1216+
* anon_vma propagation.
1217+
*/
1218+
ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size,
1219+
MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP,
1220+
&self->carveout[page_size + 3 * page_size]);
1221+
ASSERT_NE(ptr_a, MAP_FAILED);
1222+
1223+
/* The VMAs should have merged. */
1224+
ASSERT_TRUE(find_vma_procmap(procmap, ptr_b));
1225+
ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_b);
1226+
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_b + 6 * page_size);
1227+
}
1228+
1229+
TEST_F(merge, mremap_faulted_to_unfaulted_next)
1230+
{
1231+
struct procmap_fd *procmap = &self->procmap;
1232+
unsigned int page_size = self->page_size;
1233+
char *ptr_a, *ptr_b;
1234+
1235+
/*
1236+
* mremap() such that A and B merge:
1237+
*
1238+
* |---------------------------|
1239+
* | \ |
1240+
* | |-----------| / |---------|
1241+
* v | unfaulted | \ | faulted |
1242+
* |-----------| / |---------|
1243+
* B \ A
1244+
*
1245+
* Then unmap VMA A to trigger the bug.
1246+
*/
1247+
1248+
/* Map VMA A into place. */
1249+
ptr_a = mmap(&self->carveout[page_size], 3 * page_size,
1250+
PROT_READ | PROT_WRITE,
1251+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1252+
ASSERT_NE(ptr_a, MAP_FAILED);
1253+
/* Fault it in. */
1254+
ptr_a[0] = 'x';
1255+
1256+
/*
1257+
* Now move it out of the way so we can place VMA B in position,
1258+
* unfaulted.
1259+
*/
1260+
ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size,
1261+
MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]);
1262+
ASSERT_NE(ptr_a, MAP_FAILED);
1263+
1264+
/* Map VMA B into place. */
1265+
ptr_b = mmap(&self->carveout[page_size + 3 * page_size], 3 * page_size,
1266+
PROT_READ | PROT_WRITE,
1267+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1268+
ASSERT_NE(ptr_b, MAP_FAILED);
1269+
1270+
/*
1271+
* Now move VMA A into position with MREMAP_DONTUNMAP to catch incorrect
1272+
* anon_vma propagation.
1273+
*/
1274+
ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size,
1275+
MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP,
1276+
&self->carveout[page_size]);
1277+
ASSERT_NE(ptr_a, MAP_FAILED);
1278+
1279+
/* The VMAs should have merged. */
1280+
ASSERT_TRUE(find_vma_procmap(procmap, ptr_a));
1281+
ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a);
1282+
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 6 * page_size);
1283+
}
1284+
1285+
TEST_F(merge, mremap_faulted_to_unfaulted_prev_unfaulted_next)
1286+
{
1287+
struct procmap_fd *procmap = &self->procmap;
1288+
unsigned int page_size = self->page_size;
1289+
char *ptr_a, *ptr_b, *ptr_c;
1290+
1291+
/*
1292+
* mremap() with MREMAP_DONTUNMAP such that A, B and C merge:
1293+
*
1294+
* |---------------------------|
1295+
* | \ |
1296+
* |-----------| | |-----------| / |---------|
1297+
* | unfaulted | v | unfaulted | \ | faulted |
1298+
* |-----------| |-----------| / |---------|
1299+
* A C \ B
1300+
*/
1301+
1302+
/* Map VMA B into place. */
1303+
ptr_b = mmap(&self->carveout[page_size + 3 * page_size], 3 * page_size,
1304+
PROT_READ | PROT_WRITE,
1305+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1306+
ASSERT_NE(ptr_b, MAP_FAILED);
1307+
/* Fault it in. */
1308+
ptr_b[0] = 'x';
1309+
1310+
/*
1311+
* Now move it out of the way so we can place VMAs A, C in position,
1312+
* unfaulted.
1313+
*/
1314+
ptr_b = mremap(ptr_b, 3 * page_size, 3 * page_size,
1315+
MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]);
1316+
ASSERT_NE(ptr_b, MAP_FAILED);
1317+
1318+
/* Map VMA A into place. */
1319+
1320+
ptr_a = mmap(&self->carveout[page_size], 3 * page_size,
1321+
PROT_READ | PROT_WRITE,
1322+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1323+
ASSERT_NE(ptr_a, MAP_FAILED);
1324+
1325+
/* Map VMA C into place. */
1326+
ptr_c = mmap(&self->carveout[page_size + 3 * page_size + 3 * page_size],
1327+
3 * page_size, PROT_READ | PROT_WRITE,
1328+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1329+
ASSERT_NE(ptr_c, MAP_FAILED);
1330+
1331+
/*
1332+
* Now move VMA B into position with MREMAP_DONTUNMAP to catch incorrect
1333+
* anon_vma propagation.
1334+
*/
1335+
ptr_b = mremap(ptr_b, 3 * page_size, 3 * page_size,
1336+
MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP,
1337+
&self->carveout[page_size + 3 * page_size]);
1338+
ASSERT_NE(ptr_b, MAP_FAILED);
1339+
1340+
/* The VMAs should have merged. */
1341+
ASSERT_TRUE(find_vma_procmap(procmap, ptr_a));
1342+
ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a);
1343+
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size);
1344+
}
1345+
1346+
TEST_F(merge, mremap_faulted_to_unfaulted_prev_faulted_next)
1347+
{
1348+
struct procmap_fd *procmap = &self->procmap;
1349+
unsigned int page_size = self->page_size;
1350+
char *ptr_a, *ptr_b, *ptr_bc;
1351+
1352+
/*
1353+
* mremap() with MREMAP_DONTUNMAP such that A, B and C merge:
1354+
*
1355+
* |---------------------------|
1356+
* | \ |
1357+
* |-----------| | |-----------| / |---------|
1358+
* | unfaulted | v | faulted | \ | faulted |
1359+
* |-----------| |-----------| / |---------|
1360+
* A C \ B
1361+
*/
1362+
1363+
/*
1364+
* Map VMA B and C into place. We have to map them together so their
1365+
* anon_vma is the same and the vma->vm_pgoff's are correctly aligned.
1366+
*/
1367+
ptr_bc = mmap(&self->carveout[page_size + 3 * page_size],
1368+
3 * page_size + 3 * page_size,
1369+
PROT_READ | PROT_WRITE,
1370+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1371+
ASSERT_NE(ptr_bc, MAP_FAILED);
1372+
1373+
/* Fault it in. */
1374+
ptr_bc[0] = 'x';
1375+
1376+
/*
1377+
* Now move VMA B out the way (splitting VMA BC) so we can place VMA A
1378+
* in position, unfaulted, and leave the remainder of the VMA we just
1379+
* moved in place, faulted, as VMA C.
1380+
*/
1381+
ptr_b = mremap(ptr_bc, 3 * page_size, 3 * page_size,
1382+
MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]);
1383+
ASSERT_NE(ptr_b, MAP_FAILED);
1384+
1385+
/* Map VMA A into place. */
1386+
ptr_a = mmap(&self->carveout[page_size], 3 * page_size,
1387+
PROT_READ | PROT_WRITE,
1388+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
1389+
ASSERT_NE(ptr_a, MAP_FAILED);
1390+
1391+
/*
1392+
* Now move VMA B into position with MREMAP_DONTUNMAP to catch incorrect
1393+
* anon_vma propagation.
1394+
*/
1395+
ptr_b = mremap(ptr_b, 3 * page_size, 3 * page_size,
1396+
MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP,
1397+
&self->carveout[page_size + 3 * page_size]);
1398+
ASSERT_NE(ptr_b, MAP_FAILED);
1399+
1400+
/* The VMAs should have merged. */
1401+
ASSERT_TRUE(find_vma_procmap(procmap, ptr_a));
1402+
ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a);
1403+
ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size);
1404+
}
1405+
11741406
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)