Skip to content

Commit c608f5e

Browse files
i#1569 AArch64: Strip memory tags in vmareas/memquery
Android 11+ uses the scudo heap allocator [1] which always returns tagged pointers if running on hardware that supports AArch64 TBI (Top Byte Ignore) or MTE (Memory Tagging Extension). App pointers that were obtained from malloc or new will contain a tag in the top byte of the pointer. In most situations this doesn't matter to DynamoRIO, its just part of the pointer value. However, memory tags are assigned in userspace by the allocator. They are not stored in the process's page tables (they would be too granular for that) and addresses in the process's memory map do not contain tags. When DynamoRIO looks up an `app_pc` in vmareas or memquery we need to strip the tag out so it can be compared to the untagged VAs in the memory map. This was discovered when testing on AArch64 Android but the same problem happens with Linux systems using a tagging allocator. I have reproduced it on Linux with glibc with the env var `GLIBC_TUNABLES=glibc.mem.tagging=3` set [2]. This fixes asserts seen running the test linux.bad-signal-stack: dynamorio/core/unix/memcache.c:382 found CURIOSITY : !exists || vmvector_overlap(all_memory_areas, start, end) || are_dynamo_vm_areas_stale() || !dynamo_initialized in file dynamorio/core/unix/memcache.c line 340 [1] https://source.android.com/docs/security/test/scudo [2] https://www.gnu.org/software/libc/manual/html_node/Memory-Related-Tunables.html Issue: #1569, #2154
1 parent 7fb19f4 commit c608f5e

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

core/unix/memquery_linux.c

+1
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ bool
335335
memquery_from_os(const byte *pc, DR_PARAM_OUT dr_mem_info_t *info,
336336
DR_PARAM_OUT bool *have_type)
337337
{
338+
pc = STRIP_MEMORY_TAG(pc);
338339
memquery_iter_t iter;
339340
app_pc last_end = NULL;
340341
app_pc next_start = (app_pc)POINTER_MAX;

core/vmareas.c

+192
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,8 @@ add_vm_area(vm_area_vector_t *v, app_pc start, app_pc end, uint vm_flags, uint f
917917
void *data _IF_DEBUG(const char *comment))
918918
{
919919
int i, j, diff;
920+
start = STRIP_MEMORY_TAG(start);
921+
end = STRIP_MEMORY_TAG(end);
920922
/* if we have overlap, we extend an existing area -- else we add a new area */
921923
int overlap_start = -1, overlap_end = -1;
922924
DEBUG_DECLARE(uint flagignore;)
@@ -1331,6 +1333,8 @@ remove_vm_area(vm_area_vector_t *v, app_pc start, app_pc end, bool restore_prot)
13311333
* custom.frags and not custom.client
13321334
*/
13331335
bool official_coarse_vector = (v == executable_areas);
1336+
start = STRIP_MEMORY_TAG(start);
1337+
end = STRIP_MEMORY_TAG(end);
13341338

13351339
ASSERT_VMAREA_VECTOR_PROTECTED(v, WRITE);
13361340
LOG(GLOBAL, LOG_VMAREAS, 4, "in remove_vm_area " PFX " " PFX "\n", start, end);
@@ -1503,6 +1507,8 @@ static bool
15031507
binary_search(vm_area_vector_t *v, app_pc start, app_pc end, vm_area_t **area /*OUT*/,
15041508
int *index /*OUT*/, bool first)
15051509
{
1510+
start = STRIP_MEMORY_TAG(start);
1511+
end = STRIP_MEMORY_TAG(end);
15061512
/* BINARY SEARCH -- assumes the vector is kept sorted by add & remove! */
15071513
int min = 0;
15081514
int max = v->length - 1;
@@ -1579,6 +1585,8 @@ lookup_addr(vm_area_vector_t *v, app_pc addr, vm_area_t **area)
15791585
static bool
15801586
vm_area_overlap(vm_area_vector_t *v, app_pc start, app_pc end)
15811587
{
1588+
start = STRIP_MEMORY_TAG(start);
1589+
end = STRIP_MEMORY_TAG(end);
15821590
/* binary search asserts v is protected */
15831591
return binary_search(v, start, end, NULL, NULL, false);
15841592
}
@@ -1936,6 +1944,9 @@ vmvector_add_replace(vm_area_vector_t *v, app_pc start, app_pc end, void *data)
19361944
void *old_data = NULL;
19371945
bool release_lock; /* 'true' means this routine needs to unlock */
19381946

1947+
start = STRIP_MEMORY_TAG(start);
1948+
end = STRIP_MEMORY_TAG(end);
1949+
19391950
LOCK_VECTOR(v, release_lock, write);
19401951
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
19411952
overlap = lookup_addr(v, start, &area);
@@ -2098,6 +2109,8 @@ vmvector_modify_data(vm_area_vector_t *v, app_pc start, app_pc end, void *data)
20982109
bool overlap;
20992110
vm_area_t *area = NULL;
21002111
bool release_lock; /* 'true' means this routine needs to unlock */
2112+
start = STRIP_MEMORY_TAG(start);
2113+
end = STRIP_MEMORY_TAG(end);
21012114

21022115
LOCK_VECTOR(v, release_lock, write);
21032116
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
@@ -11658,6 +11671,164 @@ aslr_report_violation(app_pc execution_fault_pc, security_option_t handling_poli
1165811671
#ifdef STANDALONE_UNIT_TEST
1165911672
# define INT_TO_PC(x) ((app_pc)(ptr_uint_t)(x))
1166011673

11674+
# if defined(AARCH64)
11675+
# define TAG_INT_TO_PC(tag, addr) \
11676+
INT_TO_PC(((ptr_uint_t)tag) << 56 | ((ptr_uint_t)addr))
11677+
# endif
11678+
11679+
/* Test vmvector behaviour with tagged pointers.
11680+
* vmvector_tagged_pointer_tests() takes two pairs of app_pc values:
11681+
* create_start, create_end are used to create an area,
11682+
* lookup_start, lookup_end are used to look up the area after it has been created.
11683+
* The *_start values should have the same address and the two *_end values should have
11684+
* the same address but they can have different tag values.
11685+
* We call vmvector_tagged_pointer_tests() several times with different combinations of
11686+
* tagged and untagged pointers to test different scenarios (create tagged and lookup
11687+
* untagged, create untagged and lookup tagged, etc).
11688+
*/
11689+
void
11690+
vmvector_tagged_pointer_tests(app_pc create_start, app_pc create_end, app_pc lookup_start,
11691+
app_pc lookup_end)
11692+
{
11693+
/* Sanity check to make sure the test function is being used correctly. */
11694+
EXPECT(STRIP_MEMORY_TAG(create_start), STRIP_MEMORY_TAG(lookup_start));
11695+
EXPECT(STRIP_MEMORY_TAG(create_end), STRIP_MEMORY_TAG(lookup_end));
11696+
11697+
vm_area_vector_t v = { 0, 0, 0, VECTOR_SHARED | VECTOR_NEVER_MERGE,
11698+
INIT_READWRITE_LOCK(thread_vm_areas) };
11699+
uint data1 = 0;
11700+
uint data2 = 0;
11701+
11702+
vmvector_add(&v, create_start, create_end, &data1);
11703+
11704+
{
11705+
bool res = vmvector_overlap(&v, lookup_start, lookup_end);
11706+
EXPECT(res, true);
11707+
}
11708+
11709+
{
11710+
void *returned_data = vmvector_add_replace(&v, lookup_start, lookup_end, &data2);
11711+
EXPECT(returned_data, &data1);
11712+
}
11713+
11714+
{
11715+
void *returned_data = vmvector_lookup(&v, lookup_start);
11716+
EXPECT(returned_data, &data2);
11717+
}
11718+
11719+
{
11720+
bool res = vmvector_overlap(&v, lookup_start, lookup_end);
11721+
EXPECT(res, true);
11722+
}
11723+
11724+
{
11725+
app_pc start = NULL;
11726+
app_pc end = NULL;
11727+
void *returned_data = NULL;
11728+
11729+
bool res = vmvector_lookup_data(&v, lookup_start, &start, &end, &returned_data);
11730+
11731+
EXPECT(res, true);
11732+
EXPECT(start, STRIP_MEMORY_TAG(create_start));
11733+
EXPECT(end, STRIP_MEMORY_TAG(create_end));
11734+
EXPECT(returned_data, &data2);
11735+
}
11736+
11737+
{
11738+
bool res = vmvector_modify_data(&v, lookup_start, lookup_end, &data1);
11739+
EXPECT(res, true);
11740+
11741+
app_pc start = NULL;
11742+
app_pc end = NULL;
11743+
void *returned_data = NULL;
11744+
res = vmvector_lookup_data(&v, lookup_start, &start, &end, &returned_data);
11745+
11746+
EXPECT(res, true);
11747+
EXPECT(returned_data, &data1);
11748+
}
11749+
11750+
{
11751+
vmvector_iterator_t vmvi;
11752+
vmvector_iterator_start(&v, &vmvi);
11753+
11754+
app_pc start = NULL;
11755+
app_pc end = NULL;
11756+
void *returned_data = vmvector_iterator_peek(&vmvi, &start, &end);
11757+
11758+
EXPECT(returned_data, &data1);
11759+
EXPECT(start, STRIP_MEMORY_TAG(create_start));
11760+
EXPECT(end, STRIP_MEMORY_TAG(create_end));
11761+
11762+
vmvector_iterator_stop(&vmvi);
11763+
}
11764+
11765+
{
11766+
vmvector_iterator_t vmvi;
11767+
vmvector_iterator_start(&v, &vmvi);
11768+
11769+
app_pc start = NULL;
11770+
app_pc end = NULL;
11771+
void *returned_data = vmvector_iterator_next(&vmvi, &start, &end);
11772+
11773+
EXPECT(returned_data, &data1);
11774+
EXPECT(start, STRIP_MEMORY_TAG(create_start));
11775+
EXPECT(end, STRIP_MEMORY_TAG(create_end));
11776+
11777+
vmvector_iterator_stop(&vmvi);
11778+
}
11779+
11780+
{
11781+
bool res = vmvector_remove(&v, lookup_start, lookup_end);
11782+
EXPECT(res, true);
11783+
11784+
res = vmvector_overlap(&v, create_start, create_end);
11785+
EXPECT(res, false);
11786+
11787+
res = vmvector_overlap(&v, lookup_start, lookup_end);
11788+
EXPECT(res, false);
11789+
}
11790+
11791+
{
11792+
vmvector_add(&v, create_start, create_end, &data1);
11793+
11794+
app_pc start = NULL;
11795+
app_pc end = NULL;
11796+
vmvector_remove_containing_area(&v, lookup_start, &start, &end);
11797+
EXPECT(start, STRIP_MEMORY_TAG(create_start));
11798+
EXPECT(end, STRIP_MEMORY_TAG(create_end));
11799+
}
11800+
11801+
{
11802+
# define ADD_PC(pc, increment) (app_pc)((ptr_uint_t)pc + increment)
11803+
/* Create two regions with a gap between we can use to test
11804+
* vmvector_lookup_prev_next()
11805+
*/
11806+
vmvector_add(&v, create_start, create_end, &data1);
11807+
vmvector_add(&v, ADD_PC(create_start, 0x200), ADD_PC(create_end, 0x200), &data1);
11808+
11809+
app_pc prev_start = NULL;
11810+
app_pc prev_end = NULL;
11811+
app_pc next_start = NULL;
11812+
app_pc next_end = NULL;
11813+
bool res = vmvector_lookup_prev_next(&v, ADD_PC(lookup_start, 0x101), &prev_start,
11814+
&prev_end, &next_start, &next_end);
11815+
EXPECT(res, true);
11816+
EXPECT(prev_start, STRIP_MEMORY_TAG(create_start));
11817+
EXPECT(prev_end, STRIP_MEMORY_TAG(create_end));
11818+
EXPECT(next_start, STRIP_MEMORY_TAG(ADD_PC(create_start, 0x200)));
11819+
EXPECT(next_end, STRIP_MEMORY_TAG(ADD_PC(create_end, 0x200)));
11820+
11821+
/* Fill the gap and lookup the address again. */
11822+
vmvector_add(&v, ADD_PC(create_start, 0x100), ADD_PC(create_end, 0x100), &data1);
11823+
11824+
res = vmvector_lookup_prev_next(&v, ADD_PC(lookup_start, 0x101), &prev_start,
11825+
&prev_end, &next_start, &next_end);
11826+
EXPECT(res, false);
11827+
}
11828+
11829+
DELETE_READWRITE_LOCK(v.lock);
11830+
}
11831+
1166111832
static void
1166211833
print_vector_msg(vm_area_vector_t *v, file_t f, const char *msg)
1166311834
{
@@ -11715,6 +11886,27 @@ vmvector_tests()
1171511886
vmvector_remove(&v, INT_TO_PC(0x20), INT_TO_PC(0x210)); /* truncation allowed? */
1171611887
EXPECT(res, true);
1171711888
vmvector_print(&v, STDERR);
11889+
11890+
DELETE_READWRITE_LOCK(v.lock);
11891+
11892+
# if defined(AARCH64)
11893+
/* It is assumed that the untagged vs untagged case is covered by the tests above. */
11894+
print_file(STDERR, "tagged pointers (untagged vs tagged)\n");
11895+
vmvector_tagged_pointer_tests(INT_TO_PC(0x100), INT_TO_PC(0x1ff),
11896+
TAG_INT_TO_PC(0x11, 0x100), TAG_INT_TO_PC(0x11, 0x1ff));
11897+
11898+
print_file(STDERR, "tagged pointers (tagged vs untagged)\n");
11899+
vmvector_tagged_pointer_tests(TAG_INT_TO_PC(0x11, 0x100), TAG_INT_TO_PC(0x11, 0x1ff),
11900+
INT_TO_PC(0x100), INT_TO_PC(0x1ff));
11901+
11902+
print_file(STDERR, "tagged pointers (tagged vs tagged)\n");
11903+
vmvector_tagged_pointer_tests(TAG_INT_TO_PC(0x11, 0x100), TAG_INT_TO_PC(0x11, 0x1ff),
11904+
TAG_INT_TO_PC(0x11, 0x100), TAG_INT_TO_PC(0x11, 0x1ff));
11905+
11906+
print_file(STDERR, "tagged pointers (tagged vs different tag)\n");
11907+
vmvector_tagged_pointer_tests(TAG_INT_TO_PC(0x11, 0x100), TAG_INT_TO_PC(0x11, 0x1ff),
11908+
TAG_INT_TO_PC(0x22, 0x100), TAG_INT_TO_PC(0x22, 0x1ff));
11909+
# endif
1171811910
}
1171911911

1172011912
/* initial vector tests

core/vmareas.h

+10
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@
5252
/* really open-ended would make this wrap around to 0 */
5353
#define UNIVERSAL_REGION_END ((app_pc)POINTER_MAX)
5454

55+
#if defined(AARCH64) && defined(LINUX)
56+
/* Tagged pointers (whether based on TBI or MTE) use the top byte of the pointer as a
57+
* memory tag. AArch64 has 48/52-bit VAs with the upper bits set to 0 for userspace
58+
* addresses so we should just be able to mask the tag out to get an untagged address.
59+
*/
60+
# define STRIP_MEMORY_TAG(pc) ((app_pc)((ptr_uint_t)pc & 0x00ffffffffffffff))
61+
#else
62+
# define STRIP_MEMORY_TAG(pc) pc
63+
#endif
64+
5565
/* opaque struct */
5666
struct vm_area_t;
5767

0 commit comments

Comments
 (0)