1 | //===-- tsan_fd.cpp -------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file is a part of ThreadSanitizer (TSan), a race detector. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "tsan_fd.h" |
14 | |
15 | #include <sanitizer_common/sanitizer_atomic.h> |
16 | |
17 | #include "tsan_interceptors.h" |
18 | #include "tsan_rtl.h" |
19 | |
20 | namespace __tsan { |
21 | |
22 | const int kTableSizeL1 = 1024; |
23 | const int kTableSizeL2 = 1024; |
24 | const int kTableSize = kTableSizeL1 * kTableSizeL2; |
25 | |
26 | struct FdSync { |
27 | atomic_uint64_t rc; |
28 | }; |
29 | |
30 | struct FdDesc { |
31 | FdSync *sync; |
32 | // This is used to establish write -> epoll_wait synchronization |
33 | // where epoll_wait receives notification about the write. |
34 | atomic_uintptr_t aux_sync; // FdSync* |
35 | Tid creation_tid; |
36 | StackID creation_stack; |
37 | bool closed; |
38 | }; |
39 | |
40 | struct FdContext { |
41 | atomic_uintptr_t tab[kTableSizeL1]; |
42 | // Addresses used for synchronization. |
43 | FdSync globsync; |
44 | FdSync filesync; |
45 | FdSync socksync; |
46 | u64 connectsync; |
47 | }; |
48 | |
49 | static FdContext fdctx; |
50 | |
51 | static bool bogusfd(int fd) { |
52 | // Apparently a bogus fd value. |
53 | return fd < 0 || fd >= kTableSize; |
54 | } |
55 | |
56 | static FdSync *allocsync(ThreadState *thr, uptr pc) { |
57 | FdSync *s = (FdSync*)user_alloc_internal(thr, pc, sz: sizeof(FdSync), |
58 | align: kDefaultAlignment, signal: false); |
59 | atomic_store(a: &s->rc, v: 1, mo: memory_order_relaxed); |
60 | return s; |
61 | } |
62 | |
63 | static FdSync *ref(FdSync *s) { |
64 | if (s && atomic_load(a: &s->rc, mo: memory_order_relaxed) != (u64)-1) |
65 | atomic_fetch_add(a: &s->rc, v: 1, mo: memory_order_relaxed); |
66 | return s; |
67 | } |
68 | |
69 | static void unref(ThreadState *thr, uptr pc, FdSync *s) { |
70 | if (s && atomic_load(a: &s->rc, mo: memory_order_relaxed) != (u64)-1) { |
71 | if (atomic_fetch_sub(a: &s->rc, v: 1, mo: memory_order_acq_rel) == 1) { |
72 | CHECK_NE(s, &fdctx.globsync); |
73 | CHECK_NE(s, &fdctx.filesync); |
74 | CHECK_NE(s, &fdctx.socksync); |
75 | user_free(thr, pc, p: s, signal: false); |
76 | } |
77 | } |
78 | } |
79 | |
80 | static FdDesc *fddesc(ThreadState *thr, uptr pc, int fd) { |
81 | CHECK_GE(fd, 0); |
82 | CHECK_LT(fd, kTableSize); |
83 | atomic_uintptr_t *pl1 = &fdctx.tab[fd / kTableSizeL2]; |
84 | uptr l1 = atomic_load(a: pl1, mo: memory_order_consume); |
85 | if (l1 == 0) { |
86 | uptr size = kTableSizeL2 * sizeof(FdDesc); |
87 | // We need this to reside in user memory to properly catch races on it. |
88 | void *p = user_alloc_internal(thr, pc, sz: size, align: kDefaultAlignment, signal: false); |
89 | internal_memset(s: p, c: 0, n: size); |
90 | MemoryResetRange(thr, pc: (uptr)&fddesc, addr: (uptr)p, size); |
91 | if (atomic_compare_exchange_strong(a: pl1, cmp: &l1, xchg: (uptr)p, mo: memory_order_acq_rel)) |
92 | l1 = (uptr)p; |
93 | else |
94 | user_free(thr, pc, p, signal: false); |
95 | } |
96 | FdDesc *fds = reinterpret_cast<FdDesc *>(l1); |
97 | return &fds[fd % kTableSizeL2]; |
98 | } |
99 | |
100 | // pd must be already ref'ed. |
101 | static void init(ThreadState *thr, uptr pc, int fd, FdSync *s, |
102 | bool write = true) { |
103 | FdDesc *d = fddesc(thr, pc, fd); |
104 | // As a matter of fact, we don't intercept all close calls. |
105 | // See e.g. libc __res_iclose(). |
106 | if (d->sync) { |
107 | unref(thr, pc, s: d->sync); |
108 | d->sync = 0; |
109 | } |
110 | unref(thr, pc, |
111 | s: reinterpret_cast<FdSync *>( |
112 | atomic_load(a: &d->aux_sync, mo: memory_order_relaxed))); |
113 | atomic_store(a: &d->aux_sync, v: 0, mo: memory_order_relaxed); |
114 | if (flags()->io_sync == 0) { |
115 | unref(thr, pc, s); |
116 | } else if (flags()->io_sync == 1) { |
117 | d->sync = s; |
118 | } else if (flags()->io_sync == 2) { |
119 | unref(thr, pc, s); |
120 | d->sync = &fdctx.globsync; |
121 | } |
122 | d->creation_tid = thr->tid; |
123 | d->creation_stack = CurrentStackId(thr, pc); |
124 | d->closed = false; |
125 | // This prevents false positives on fd_close_norace3.cpp test. |
126 | // The mechanics of the false positive are not completely clear, |
127 | // but it happens only if global reset is enabled (flush_memory_ms=1) |
128 | // and may be related to lost writes during asynchronous MADV_DONTNEED. |
129 | SlotLocker locker(thr); |
130 | if (write) { |
131 | // To catch races between fd usage and open. |
132 | MemoryRangeImitateWrite(thr, pc, addr: (uptr)d, size: 8); |
133 | } else { |
134 | // See the dup-related comment in FdClose. |
135 | MemoryAccess(thr, pc, addr: (uptr)d, size: 8, typ: kAccessRead | kAccessSlotLocked); |
136 | } |
137 | } |
138 | |
139 | void FdInit() { |
140 | atomic_store(a: &fdctx.globsync.rc, v: (u64)-1, mo: memory_order_relaxed); |
141 | atomic_store(a: &fdctx.filesync.rc, v: (u64)-1, mo: memory_order_relaxed); |
142 | atomic_store(a: &fdctx.socksync.rc, v: (u64)-1, mo: memory_order_relaxed); |
143 | } |
144 | |
145 | void FdOnFork(ThreadState *thr, uptr pc) { |
146 | // On fork() we need to reset all fd's, because the child is going |
147 | // close all them, and that will cause races between previous read/write |
148 | // and the close. |
149 | for (int l1 = 0; l1 < kTableSizeL1; l1++) { |
150 | FdDesc *tab = (FdDesc*)atomic_load(a: &fdctx.tab[l1], mo: memory_order_relaxed); |
151 | if (tab == 0) |
152 | break; |
153 | for (int l2 = 0; l2 < kTableSizeL2; l2++) { |
154 | FdDesc *d = &tab[l2]; |
155 | MemoryResetRange(thr, pc, addr: (uptr)d, size: 8); |
156 | } |
157 | } |
158 | } |
159 | |
160 | bool FdLocation(uptr addr, int *fd, Tid *tid, StackID *stack, bool *closed) { |
161 | for (int l1 = 0; l1 < kTableSizeL1; l1++) { |
162 | FdDesc *tab = (FdDesc*)atomic_load(a: &fdctx.tab[l1], mo: memory_order_relaxed); |
163 | if (tab == 0) |
164 | break; |
165 | if (addr >= (uptr)tab && addr < (uptr)(tab + kTableSizeL2)) { |
166 | int l2 = (addr - (uptr)tab) / sizeof(FdDesc); |
167 | FdDesc *d = &tab[l2]; |
168 | *fd = l1 * kTableSizeL1 + l2; |
169 | *tid = d->creation_tid; |
170 | *stack = d->creation_stack; |
171 | *closed = d->closed; |
172 | return true; |
173 | } |
174 | } |
175 | return false; |
176 | } |
177 | |
178 | void FdAcquire(ThreadState *thr, uptr pc, int fd) { |
179 | if (bogusfd(fd)) |
180 | return; |
181 | FdDesc *d = fddesc(thr, pc, fd); |
182 | FdSync *s = d->sync; |
183 | DPrintf("#%d: FdAcquire(%d) -> %p\n" , thr->tid, fd, s); |
184 | MemoryAccess(thr, pc, addr: (uptr)d, size: 8, typ: kAccessRead); |
185 | if (s) |
186 | Acquire(thr, pc, addr: (uptr)s); |
187 | } |
188 | |
189 | void FdRelease(ThreadState *thr, uptr pc, int fd) { |
190 | if (bogusfd(fd)) |
191 | return; |
192 | FdDesc *d = fddesc(thr, pc, fd); |
193 | FdSync *s = d->sync; |
194 | DPrintf("#%d: FdRelease(%d) -> %p\n" , thr->tid, fd, s); |
195 | MemoryAccess(thr, pc, addr: (uptr)d, size: 8, typ: kAccessRead); |
196 | if (s) |
197 | Release(thr, pc, addr: (uptr)s); |
198 | if (uptr aux_sync = atomic_load(a: &d->aux_sync, mo: memory_order_acquire)) |
199 | Release(thr, pc, addr: aux_sync); |
200 | } |
201 | |
202 | void FdAccess(ThreadState *thr, uptr pc, int fd) { |
203 | DPrintf("#%d: FdAccess(%d)\n" , thr->tid, fd); |
204 | if (bogusfd(fd)) |
205 | return; |
206 | FdDesc *d = fddesc(thr, pc, fd); |
207 | MemoryAccess(thr, pc, addr: (uptr)d, size: 8, typ: kAccessRead); |
208 | } |
209 | |
210 | void FdClose(ThreadState *thr, uptr pc, int fd, bool write) { |
211 | DPrintf("#%d: FdClose(%d)\n" , thr->tid, fd); |
212 | if (bogusfd(fd)) |
213 | return; |
214 | FdDesc *d = fddesc(thr, pc, fd); |
215 | { |
216 | // Need to lock the slot to make MemoryAccess and MemoryResetRange atomic |
217 | // with respect to global reset. See the comment in MemoryRangeFreed. |
218 | SlotLocker locker(thr); |
219 | if (!MustIgnoreInterceptor(thr)) { |
220 | if (write) { |
221 | // To catch races between fd usage and close. |
222 | MemoryAccess(thr, pc, addr: (uptr)d, size: 8, |
223 | typ: kAccessWrite | kAccessCheckOnly | kAccessSlotLocked); |
224 | } else { |
225 | // This path is used only by dup2/dup3 calls. |
226 | // We do read instead of write because there is a number of legitimate |
227 | // cases where write would lead to false positives: |
228 | // 1. Some software dups a closed pipe in place of a socket before |
229 | // closing |
230 | // the socket (to prevent races actually). |
231 | // 2. Some daemons dup /dev/null in place of stdin/stdout. |
232 | // On the other hand we have not seen cases when write here catches real |
233 | // bugs. |
234 | MemoryAccess(thr, pc, addr: (uptr)d, size: 8, |
235 | typ: kAccessRead | kAccessCheckOnly | kAccessSlotLocked); |
236 | } |
237 | } |
238 | // We need to clear it, because if we do not intercept any call out there |
239 | // that creates fd, we will hit false postives. |
240 | MemoryResetRange(thr, pc, addr: (uptr)d, size: 8); |
241 | } |
242 | unref(thr, pc, s: d->sync); |
243 | d->sync = 0; |
244 | unref(thr, pc, |
245 | s: reinterpret_cast<FdSync *>( |
246 | atomic_load(a: &d->aux_sync, mo: memory_order_relaxed))); |
247 | atomic_store(a: &d->aux_sync, v: 0, mo: memory_order_relaxed); |
248 | d->closed = true; |
249 | d->creation_tid = thr->tid; |
250 | d->creation_stack = CurrentStackId(thr, pc); |
251 | } |
252 | |
253 | void FdFileCreate(ThreadState *thr, uptr pc, int fd) { |
254 | DPrintf("#%d: FdFileCreate(%d)\n" , thr->tid, fd); |
255 | if (bogusfd(fd)) |
256 | return; |
257 | init(thr, pc, fd, s: &fdctx.filesync); |
258 | } |
259 | |
260 | void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd, bool write) { |
261 | DPrintf("#%d: FdDup(%d, %d)\n" , thr->tid, oldfd, newfd); |
262 | if (bogusfd(fd: oldfd) || bogusfd(fd: newfd)) |
263 | return; |
264 | // Ignore the case when user dups not yet connected socket. |
265 | FdDesc *od = fddesc(thr, pc, fd: oldfd); |
266 | MemoryAccess(thr, pc, addr: (uptr)od, size: 8, typ: kAccessRead); |
267 | FdClose(thr, pc, fd: newfd, write); |
268 | init(thr, pc, fd: newfd, s: ref(s: od->sync), write); |
269 | } |
270 | |
271 | void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd) { |
272 | DPrintf("#%d: FdCreatePipe(%d, %d)\n" , thr->tid, rfd, wfd); |
273 | FdSync *s = allocsync(thr, pc); |
274 | init(thr, pc, fd: rfd, s: ref(s)); |
275 | init(thr, pc, fd: wfd, s: ref(s)); |
276 | unref(thr, pc, s); |
277 | } |
278 | |
279 | void FdEventCreate(ThreadState *thr, uptr pc, int fd) { |
280 | DPrintf("#%d: FdEventCreate(%d)\n" , thr->tid, fd); |
281 | if (bogusfd(fd)) |
282 | return; |
283 | init(thr, pc, fd, s: allocsync(thr, pc)); |
284 | } |
285 | |
286 | void FdSignalCreate(ThreadState *thr, uptr pc, int fd) { |
287 | DPrintf("#%d: FdSignalCreate(%d)\n" , thr->tid, fd); |
288 | if (bogusfd(fd)) |
289 | return; |
290 | init(thr, pc, fd, s: 0); |
291 | } |
292 | |
293 | void FdInotifyCreate(ThreadState *thr, uptr pc, int fd) { |
294 | DPrintf("#%d: FdInotifyCreate(%d)\n" , thr->tid, fd); |
295 | if (bogusfd(fd)) |
296 | return; |
297 | init(thr, pc, fd, s: 0); |
298 | } |
299 | |
300 | void FdPollCreate(ThreadState *thr, uptr pc, int fd) { |
301 | DPrintf("#%d: FdPollCreate(%d)\n" , thr->tid, fd); |
302 | if (bogusfd(fd)) |
303 | return; |
304 | init(thr, pc, fd, s: allocsync(thr, pc)); |
305 | } |
306 | |
307 | void FdPollAdd(ThreadState *thr, uptr pc, int epfd, int fd) { |
308 | DPrintf("#%d: FdPollAdd(%d, %d)\n" , thr->tid, epfd, fd); |
309 | if (bogusfd(fd: epfd) || bogusfd(fd)) |
310 | return; |
311 | FdDesc *d = fddesc(thr, pc, fd); |
312 | // Associate fd with epoll fd only once. |
313 | // While an fd can be associated with multiple epolls at the same time, |
314 | // or with different epolls during different phases of lifetime, |
315 | // synchronization semantics (and examples) of this are unclear. |
316 | // So we don't support this for now. |
317 | // If we change the association, it will also create lifetime management |
318 | // problem for FdRelease which accesses the aux_sync. |
319 | if (atomic_load(a: &d->aux_sync, mo: memory_order_relaxed)) |
320 | return; |
321 | FdDesc *epd = fddesc(thr, pc, fd: epfd); |
322 | FdSync *s = epd->sync; |
323 | if (!s) |
324 | return; |
325 | uptr cmp = 0; |
326 | if (atomic_compare_exchange_strong( |
327 | a: &d->aux_sync, cmp: &cmp, xchg: reinterpret_cast<uptr>(s), mo: memory_order_release)) |
328 | ref(s); |
329 | } |
330 | |
331 | void FdSocketCreate(ThreadState *thr, uptr pc, int fd) { |
332 | DPrintf("#%d: FdSocketCreate(%d)\n" , thr->tid, fd); |
333 | if (bogusfd(fd)) |
334 | return; |
335 | // It can be a UDP socket. |
336 | init(thr, pc, fd, s: &fdctx.socksync); |
337 | } |
338 | |
339 | void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd) { |
340 | DPrintf("#%d: FdSocketAccept(%d, %d)\n" , thr->tid, fd, newfd); |
341 | if (bogusfd(fd)) |
342 | return; |
343 | // Synchronize connect->accept. |
344 | Acquire(thr, pc, addr: (uptr)&fdctx.connectsync); |
345 | init(thr, pc, fd: newfd, s: &fdctx.socksync); |
346 | } |
347 | |
348 | void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) { |
349 | DPrintf("#%d: FdSocketConnecting(%d)\n" , thr->tid, fd); |
350 | if (bogusfd(fd)) |
351 | return; |
352 | // Synchronize connect->accept. |
353 | Release(thr, pc, addr: (uptr)&fdctx.connectsync); |
354 | } |
355 | |
356 | void FdSocketConnect(ThreadState *thr, uptr pc, int fd) { |
357 | DPrintf("#%d: FdSocketConnect(%d)\n" , thr->tid, fd); |
358 | if (bogusfd(fd)) |
359 | return; |
360 | init(thr, pc, fd, s: &fdctx.socksync); |
361 | } |
362 | |
363 | uptr File2addr(const char *path) { |
364 | (void)path; |
365 | static u64 addr; |
366 | return (uptr)&addr; |
367 | } |
368 | |
369 | uptr Dir2addr(const char *path) { |
370 | (void)path; |
371 | static u64 addr; |
372 | return (uptr)&addr; |
373 | } |
374 | |
375 | } // namespace __tsan |
376 | |