1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3
4 #include <mach/mach_init.h>
5 #include <mach/vm_map.h>
6
7 T_GLOBAL_META(
8 T_META_NAMESPACE("xnu.vm"),
9 T_META_RADAR_COMPONENT_NAME("xnu"),
10 T_META_RADAR_COMPONENT_VERSION("VM"));
11
12 struct context1 {
13 vm_size_t obj_size;
14 vm_address_t e0;
15 dispatch_semaphore_t running_sem;
16 pthread_mutex_t mtx;
17 bool done;
18 };
19
20 static void *
protect_thread(__unused void * arg)21 protect_thread(__unused void *arg)
22 {
23 kern_return_t kr;
24 struct context1 *ctx;
25
26 ctx = (struct context1 *)arg;
27 /* tell main thread we're ready to run */
28 dispatch_semaphore_signal(ctx->running_sem);
29 while (!ctx->done) {
30 /* wait for main thread to be done setting things up */
31 pthread_mutex_lock(&ctx->mtx);
32 /* make 2nd target mapping (e0) read-only */
33 kr = vm_protect(mach_task_self(),
34 ctx->e0,
35 ctx->obj_size,
36 FALSE, /* set_maximum */
37 VM_PROT_READ);
38 T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_protect() RO");
39 /* wait a little bit */
40 usleep(100);
41 /* make it read-write again */
42 kr = vm_protect(mach_task_self(),
43 ctx->e0,
44 ctx->obj_size,
45 FALSE, /* set_maximum */
46 VM_PROT_READ | VM_PROT_WRITE);
47 T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_protect() RW");
48 /* tell main thread we're done changing protections */
49 pthread_mutex_unlock(&ctx->mtx);
50 usleep(100);
51 }
52 return NULL;
53 }
54
55 T_DECL(unaligned_write_to_cow_bypass,
56 "Test that unaligned copy respects COW")
57 {
58 pthread_t th = NULL;
59 int ret;
60 kern_return_t kr;
61 time_t start, duration;
62 mach_msg_type_number_t cow_read_size;
63 vm_size_t copied_size;
64 int loops;
65 vm_address_t e1, e2, e5;
66 struct context1 context1, *ctx;
67 int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
68
69 ctx = &context1;
70 ctx->obj_size = 256 * 1024;
71 ctx->e0 = 0;
72 ctx->running_sem = dispatch_semaphore_create(0);
73 T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
74 ret = pthread_mutex_init(&ctx->mtx, NULL);
75 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
76 ctx->done = false;
77
78 pthread_mutex_lock(&ctx->mtx);
79
80 /* start racing thread */
81 ret = pthread_create(&th, NULL, protect_thread, (void *)ctx);
82 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
83
84 /* wait for racing thread to be ready to run */
85 dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);
86
87 duration = 10; /* 10 seconds */
88 T_LOG("Testing for %ld seconds...", duration);
89 for (start = time(NULL), loops = 0;
90 time(NULL) < start + duration;
91 loops++) {
92 /* reserve space for our 2 contiguous allocations */
93 e2 = 0;
94 kr = vm_allocate(mach_task_self(),
95 &e2,
96 2 * ctx->obj_size,
97 VM_FLAGS_ANYWHERE);
98 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
99
100 /* make 1st allocation in our reserved space */
101 kr = vm_allocate(mach_task_self(),
102 &e2,
103 ctx->obj_size,
104 VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
105 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
106 /* initialize to 'B' */
107 memset((char *)e2, 'B', ctx->obj_size);
108
109 /* make 2nd allocation in our reserved space */
110 ctx->e0 = e2 + ctx->obj_size;
111 kr = vm_allocate(mach_task_self(),
112 &ctx->e0,
113 ctx->obj_size,
114 VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241));
115 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e0");
116 memset((char *)ctx->e0, 'A', ctx->obj_size);
117 /* initialize to 'A' */
118
119 /* make a COW copy of e0 */
120 e1 = 0;
121 kr = vm_read(mach_task_self(),
122 ctx->e0,
123 ctx->obj_size,
124 &e1,
125 &cow_read_size);
126 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_read e0->e1");
127
128 /* allocate a source buffer */
129 kr = vm_allocate(mach_task_self(),
130 &e5,
131 ctx->obj_size,
132 VM_FLAGS_ANYWHERE);
133 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
134 /* initialize to 'C' */
135 memset((char *)e5, 'C', ctx->obj_size);
136
137 /* let the racing thread go */
138 pthread_mutex_unlock(&ctx->mtx);
139
140 /* trigger copy_unaligned while racing with other thread */
141 kr = vm_read_overwrite(mach_task_self(),
142 e5,
143 ctx->obj_size,
144 e2 + 1,
145 &copied_size);
146 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
147 "vm_read_overwrite kr %d", kr);
148 switch (kr) {
149 case KERN_SUCCESS:
150 /* the target as RW */
151 kern_success++;
152 break;
153 case KERN_PROTECTION_FAILURE:
154 /* the target was RO */
155 kern_protection_failure++;
156 break;
157 default:
158 /* should not happen */
159 kern_other++;
160 break;
161 }
162
163 /* check that the COW copy of e0 (at e1) was not modified */
164 T_QUIET; T_ASSERT_EQ(*(char *)e1, 'A', "COW mapping was modified");
165
166 /* tell racing thread to stop toggling protections */
167 pthread_mutex_lock(&ctx->mtx);
168
169 /* clean up before next loop */
170 vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
171 ctx->e0 = 0;
172 vm_deallocate(mach_task_self(), e1, ctx->obj_size);
173 e1 = 0;
174 vm_deallocate(mach_task_self(), e2, ctx->obj_size);
175 e2 = 0;
176 vm_deallocate(mach_task_self(), e5, ctx->obj_size);
177 e5 = 0;
178 }
179
180 ctx->done = true;
181 pthread_join(th, NULL);
182
183 T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
184 kern_success, kern_protection_failure, kern_other);
185 T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
186 }
187