Auto merge of #128384 - dheaton-arm:mte-test, r= · rust-lang/rust@ff7236e (original) (raw)

File tree

11 files changed

lines changed

11 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -170,6 +170,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
170 170 "only-32bit",
171 171 "only-64bit",
172 172 "only-aarch64",
173 +"only-aarch64-unknown-linux-gnu",
173 174 "only-apple",
174 175 "only-arm",
175 176 "only-avr",
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
1 +#ifndef __BAR_H
2 +#define __BAR_H
3 +
4 +#include <sys/mman.h>
5 +#include <sys/auxv.h>
6 +#include <sys/prctl.h>
7 +#include <unistd.h>
8 +#include <stdio.h>
9 +
10 +// Set the allocation tag on the destination address using the STG instruction.
11 +#define set_tag(tagged_addr) do { \
12 + asm volatile("stg %0, [%0]" : : "r" (tagged_addr) : "memory"); \
13 +} while (0)
14 +
15 +int mte_enabled() {
16 +return (getauxval(AT_HWCAP2)) & HWCAP2_MTE;
17 +}
18 +
19 +void *alloc_page() {
20 +// Enable MTE with synchronous checking
21 +if (prctl(PR_SET_TAGGED_ADDR_CTRL,
22 +PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC (0xfffe << PR_MTE_TAG_SHIFT),
23 +0, 0, 0))
24 + {
25 +perror("prctl() failed");
26 + }
27 +
28 +// Using `mmap` allows us to ensure that, on systems which support MTE, the allocated
29 +// memory is 16-byte aligned for MTE.
30 +// This also allows us to explicitly specify whether the region should be protected by
31 +// MTE or not.
32 +if (mte_enabled()) {
33 +void *ptr = mmap(NULL, sysconf(_SC_PAGESIZE),
34 +PROT_READ | PROT_WRITE PROT_MTE, MAP_PRIVATE
35 +-1, 0);
36 + } else {
37 +void *ptr = mmap(NULL, sysconf(_SC_PAGESIZE),
38 +PROT_READ | PROT_WRITE, MAP_PRIVATE MAP_ANONYMOUS,
39 +-1, 0);
40 + }
41 +}
42 +
43 +#endif // __BAR_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
1 +#include <stdio.h>
2 +#include <stdlib.h>
3 +#include <stdint.h>
4 +#include "bar.h"
5 +
6 +extern void foo(char*);
7 +
8 +void bar(char *ptr) {
9 +if (((uintptr_t)ptr >> 56) != 0x1f) {
10 +fprintf(stderr, "Top byte corrupted on Rust -> C FFI boundary!\n");
11 +exit(1);
12 + }
13 +}
14 +
15 +int main(void)
16 +{
17 +float *ptr = alloc_page();
18 +if (ptr == MAP_FAILED)
19 + {
20 +perror("mmap() failed");
21 +return EXIT_FAILURE;
22 + }
23 +
24 +// Store an arbitrary tag in bits 56-59 of the pointer (where an MTE tag may be),
25 +// and a different value in the ignored top 4 bits.
26 +ptr = (float *)((uintptr_t)ptr | 0x1fl << 56);
27 +
28 +if (mte_enabled()) {
29 +set_tag(ptr);
30 + }
31 +
32 +ptr[0] = 2.0f;
33 +ptr[1] = 1.5f;
34 +
35 +foo(ptr); // should change the contents of the page and call `bar`
36 +
37 +if (ptr[0] != 0.5f |
38 +fprintf(stderr, "invalid data in memory; expected '0.5 0.2', got '%f %f'\n",
39 +ptr[0], ptr[1]);
40 +return EXIT_FAILURE;
41 + }
42 +
43 +return 0;
44 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
1 +#include <stdio.h>
2 +#include <stdlib.h>
3 +#include <stdint.h>
4 +#include "bar.h"
5 +
6 +typedef void (*fp)(int (*)());
7 +
8 +extern void foo(fp);
9 +
10 +void bar(int (*ptr)()) {
11 +if (((uintptr_t)ptr >> 56) != 0x2f) {
12 +fprintf(stderr, "Top byte corrupted on Rust -> C FFI boundary!\n");
13 +exit(1);
14 + }
15 +
16 +int r = (*ptr)();
17 +if (r != 32) {
18 +fprintf(stderr, "invalid return value; expected 32, got '%d'\n", r);
19 +exit(1);
20 + }
21 +}
22 +
23 +int main(void)
24 +{
25 +fp ptr = alloc_page();
26 +if (ptr == MAP_FAILED)
27 + {
28 +perror("mmap() failed");
29 +return EXIT_FAILURE;
30 + }
31 +
32 +// Store an arbitrary tag in bits 56-59 of the pointer (where an MTE tag may be),
33 +// and a different value in the ignored top 4 bits.
34 +ptr = (fp)((uintptr_t)&bar | 0x1fl << 56);
35 +
36 +foo(ptr);
37 +
38 +return 0;
39 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
1 +#include <stdio.h>
2 +#include <stdlib.h>
3 +#include <stdint.h>
4 +#include "bar.h"
5 +
6 +extern void foo(unsigned int *);
7 +
8 +void bar(char *ptr) {
9 +if (((uintptr_t)ptr >> 56) != 0x1f) {
10 +fprintf(stderr, "Top byte corrupted on Rust -> C FFI boundary!\n");
11 +exit(1);
12 + }
13 +}
14 +
15 +int main(void)
16 +{
17 +// Construct a pointer with an arbitrary tag in bits 56-59, simulating an MTE tag.
18 +// It's only necessary that the tag is preserved across FFI bounds for this test.
19 +unsigned int *ptr;
20 +
21 +ptr = alloc_page();
22 +if (ptr == MAP_FAILED)
23 + {
24 +perror("mmap() failed");
25 +return EXIT_FAILURE;
26 + }
27 +
28 +// Store an arbitrary tag in bits 56-59 of the pointer (where an MTE tag may be),
29 +// and a different value in the ignored top 4 bits.
30 +ptr = (unsigned int *)((uintptr_t)ptr | 0x1fl << 56);
31 +
32 +if (mte_enabled()) {
33 +set_tag(ptr);
34 + }
35 +
36 +ptr[0] = 61;
37 +ptr[1] = 62;
38 +
39 +foo(ptr); // should change the contents of the page to start with 0x63 0x64 and call `bar`
40 +
41 +if (ptr[0] != 0x63 |
42 +fprintf(stderr, "invalid data in memory; expected '63 64', got '%d %d'\n", ptr[0], ptr[1]);
43 +return EXIT_FAILURE;
44 + }
45 +
46 +return 0;
47 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
1 +#include <stdio.h>
2 +#include <stdlib.h>
3 +#include <stdint.h>
4 +#include "bar.h"
5 +
6 +extern void foo(char*);
7 +
8 +void bar(char *ptr) {
9 +if (((uintptr_t)ptr >> 56) != 0x2f) {
10 +fprintf(stderr, "Top byte corrupted on Rust -> C FFI boundary!\n");
11 +exit(1);
12 + }
13 +
14 +if (strcmp(ptr, "cd")) {
15 +fprintf(stderr, "invalid data in memory; expected 'cd', got '%s'\n", ptr);
16 +exit(1);
17 + }
18 +}
19 +
20 +int main(void)
21 +{
22 +// Construct a pointer with an arbitrary tag in bits 56-59, simulating an MTE tag.
23 +// It's only necessary that the tag is preserved across FFI bounds for this test.
24 +char *ptr;
25 +
26 +ptr = alloc_page();
27 +if (ptr == MAP_FAILED)
28 + {
29 +perror("mmap() failed");
30 +return EXIT_FAILURE;
31 + }
32 +
33 +// Store an arbitrary tag in bits 56-59 of the pointer (where an MTE tag may be),
34 +// and a different value in the ignored top 4 bits.
35 +ptr = (unsigned int *)((uintptr_t)ptr | 0x1fl << 56);
36 +
37 +if (mte_enabled()) {
38 +set_tag(ptr);
39 + }
40 +
41 +ptr[0] = 'a';
42 +ptr[1] = 'b';
43 +ptr[2] = '\0';
44 +
45 +foo(ptr);
46 +
47 +return 0;
48 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
1 +#![crate_type = "cdylib"]
2 +#![crate_name = "foo"]
3 +
4 +use std::os::raw::c_float;
5 +
6 +extern "C" {
7 +fn bar(ptr: *const c_float);
8 +}
9 +
10 +#[no_mangle]
11 +pub extern "C" fn foo(ptr: *mut c_float) {
12 +assert_eq!((ptr as usize) >> 56, 0x1f);
13 +
14 +unsafe {
15 +*ptr = 0.5;
16 +*ptr.wrapping_add(1) = 0.2;
17 +bar(ptr);
18 +}
19 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
1 +#![crate_type = "cdylib"]
2 +#![crate_name = "foo"]
3 +
4 +extern "C" fn ret32() -> i32 {
5 +32
6 +}
7 +
8 +#[no_mangle]
9 +pub extern "C" fn foo(ptr: extern "C" fn(extern "C" fn() -> i32)) {
10 +assert_eq!((ptr as usize) >> 56, 0x1f);
11 +
12 +// Store an arbitrary tag in the tag bits, and convert back to the correct pointer type.
13 +let p = ((ret32 as usize) | (0x2f << 56)) as *const ();
14 +let p: extern "C" fn() -> i32 = unsafe { std::mem::transmute(p) };
15 +
16 +unsafe { ptr(p) }
17 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
1 +#![crate_type = "cdylib"]
2 +#![crate_name = "foo"]
3 +
4 +use std::os::raw::c_uint;
5 +
6 +extern "C" {
7 +fn bar(ptr: *const c_uint);
8 +}
9 +
10 +#[no_mangle]
11 +pub extern "C" fn foo(ptr: *mut c_uint) {
12 +assert_eq!((ptr as usize) >> 56, 0x1f);
13 +
14 +unsafe {
15 +*ptr = 0x63;
16 +*ptr.wrapping_add(1) = 0x64;
17 +bar(ptr);
18 +}
19 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
1 +#![crate_type = "cdylib"]
2 +#![crate_name = "foo"]
3 +
4 +use std::arch::asm;
5 +use std::ffi::{CStr, CString};
6 +use std::os::raw::c_char;
7 +
8 +extern "C" {
9 +fn bar(ptr: *const c_char);
10 +}
11 +
12 +#[no_mangle]
13 +pub extern "C" fn foo(ptr: *const c_char) {
14 +assert_eq!((ptr as usize) >> 56, 0x1f);
15 +
16 +let s = unsafe { CStr::from_ptr(ptr) };
17 +assert_eq!(s.to_str().unwrap(), "ab");
18 +
19 +let s = CString::from_vec_with_nul("cd\0".into()).unwrap();
20 +let mut p = ((s.as_ptr() as usize) | (0x2f << 56)) as *const c_char;
21 +unsafe {
22 +#[cfg(target_feature = "mte")]
23 +asm!("stg {p}, [{p}]", p = inout(reg) p);
24 +
25 +bar(p);
26 +}
27 +}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
1 +// Tests that MTE tags and values stored in the top byte of a pointer (TBI) are
2 +// preserved across FFI boundaries (C <-> Rust).
3 +// This test does not require MTE: whilst the test will use MTE if available, if it is not,
4 +// arbitrary tag bits are set using TBI.
5 +
6 +// This test is only valid for AArch64.
7 +// The linker must be explicitly specified when cross-compiling, so it is limited to
8 +// `aarch64-unknown-linux-gnu`.
9 +//@ only-aarch64-unknown-linux-gnu
10 +
11 +use run_make_support::{cc, dynamic_lib_name, extra_c_flags, run, rustc, target};
12 +
13 +fn main() {
14 +run_test("int");
15 +run_test("float");
16 +run_test("string");
17 +run_test("function");
18 +}
19 +
20 +fn run_test(variant: &str) {
21 +let flags = {
22 +let mut flags = extra_c_flags();
23 + flags.push("-march=armv8.5-a+memtag");
24 + flags
25 +};
26 +println!("{variant} test...");
27 +rustc()
28 +.input(format!("foo_{variant}.rs"))
29 +.target(target())
30 +.linker("aarch64-linux-gnu-gcc")
31 +.run();
32 +cc().input(format!("bar_{variant}.c"))
33 +.input(dynamic_lib_name("foo"))
34 +.out_exe("test")
35 +.args(&flags)
36 +.run();
37 +run("test");
38 +}