Path: blob/main/contrib/llvm-project/compiler-rt/lib/tsan/rtl/tsan_fd.cpp
35268 views
//===-- tsan_fd.cpp -------------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file is a part of ThreadSanitizer (TSan), a race detector.9//10//===----------------------------------------------------------------------===//1112#include "tsan_fd.h"1314#include <sanitizer_common/sanitizer_atomic.h>1516#include "tsan_interceptors.h"17#include "tsan_rtl.h"1819namespace __tsan {2021const int kTableSizeL1 = 1024;22const int kTableSizeL2 = 1024;23const int kTableSize = kTableSizeL1 * kTableSizeL2;2425struct FdSync {26atomic_uint64_t rc;27};2829struct FdDesc {30FdSync *sync;31// This is used to establish write -> epoll_wait synchronization32// where epoll_wait receives notification about the write.33atomic_uintptr_t aux_sync; // FdSync*34Tid creation_tid;35StackID creation_stack;36bool closed;37};3839struct FdContext {40atomic_uintptr_t tab[kTableSizeL1];41// Addresses used for synchronization.42FdSync globsync;43FdSync filesync;44FdSync socksync;45u64 connectsync;46};4748static FdContext fdctx;4950static bool bogusfd(int fd) {51// Apparently a bogus fd value.52return fd < 0 || fd >= kTableSize;53}5455static FdSync *allocsync(ThreadState *thr, uptr pc) {56FdSync *s = (FdSync*)user_alloc_internal(thr, pc, sizeof(FdSync),57kDefaultAlignment, false);58atomic_store(&s->rc, 1, memory_order_relaxed);59return s;60}6162static FdSync *ref(FdSync *s) {63if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1)64atomic_fetch_add(&s->rc, 1, memory_order_relaxed);65return s;66}6768static void unref(ThreadState *thr, uptr pc, FdSync *s) {69if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) {70if (atomic_fetch_sub(&s->rc, 1, memory_order_acq_rel) == 1) {71CHECK_NE(s, &fdctx.globsync);72CHECK_NE(s, &fdctx.filesync);73CHECK_NE(s, &fdctx.socksync);74user_free(thr, pc, s, false);75}76}77}7879static FdDesc *fddesc(ThreadState *thr, uptr pc, int fd) {80CHECK_GE(fd, 0);81CHECK_LT(fd, kTableSize);82atomic_uintptr_t *pl1 = &fdctx.tab[fd / kTableSizeL2];83uptr l1 = atomic_load(pl1, memory_order_consume);84if (l1 == 0) {85uptr size = kTableSizeL2 * sizeof(FdDesc);86// We need this to reside in user memory to properly catch races on it.87void *p = user_alloc_internal(thr, pc, size, kDefaultAlignment, false);88internal_memset(p, 0, size);89MemoryResetRange(thr, (uptr)&fddesc, (uptr)p, size);90if (atomic_compare_exchange_strong(pl1, &l1, (uptr)p, memory_order_acq_rel))91l1 = (uptr)p;92else93user_free(thr, pc, p, false);94}95FdDesc *fds = reinterpret_cast<FdDesc *>(l1);96return &fds[fd % kTableSizeL2];97}9899// pd must be already ref'ed.100static void init(ThreadState *thr, uptr pc, int fd, FdSync *s,101bool write = true) {102FdDesc *d = fddesc(thr, pc, fd);103// As a matter of fact, we don't intercept all close calls.104// See e.g. libc __res_iclose().105if (d->sync) {106unref(thr, pc, d->sync);107d->sync = 0;108}109unref(thr, pc,110reinterpret_cast<FdSync *>(111atomic_load(&d->aux_sync, memory_order_relaxed)));112atomic_store(&d->aux_sync, 0, memory_order_relaxed);113if (flags()->io_sync == 0) {114unref(thr, pc, s);115} else if (flags()->io_sync == 1) {116d->sync = s;117} else if (flags()->io_sync == 2) {118unref(thr, pc, s);119d->sync = &fdctx.globsync;120}121d->creation_tid = thr->tid;122d->creation_stack = CurrentStackId(thr, pc);123d->closed = false;124// This prevents false positives on fd_close_norace3.cpp test.125// The mechanics of the false positive are not completely clear,126// but it happens only if global reset is enabled (flush_memory_ms=1)127// and may be related to lost writes during asynchronous MADV_DONTNEED.128SlotLocker locker(thr);129if (write) {130// To catch races between fd usage and open.131MemoryRangeImitateWrite(thr, pc, (uptr)d, 8);132} else {133// See the dup-related comment in FdClose.134MemoryAccess(thr, pc, (uptr)d, 8, kAccessRead | kAccessSlotLocked);135}136}137138void FdInit() {139atomic_store(&fdctx.globsync.rc, (u64)-1, memory_order_relaxed);140atomic_store(&fdctx.filesync.rc, (u64)-1, memory_order_relaxed);141atomic_store(&fdctx.socksync.rc, (u64)-1, memory_order_relaxed);142}143144void FdOnFork(ThreadState *thr, uptr pc) {145// On fork() we need to reset all fd's, because the child is going146// close all them, and that will cause races between previous read/write147// and the close.148for (int l1 = 0; l1 < kTableSizeL1; l1++) {149FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed);150if (tab == 0)151break;152for (int l2 = 0; l2 < kTableSizeL2; l2++) {153FdDesc *d = &tab[l2];154MemoryResetRange(thr, pc, (uptr)d, 8);155}156}157}158159bool FdLocation(uptr addr, int *fd, Tid *tid, StackID *stack, bool *closed) {160for (int l1 = 0; l1 < kTableSizeL1; l1++) {161FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed);162if (tab == 0)163break;164if (addr >= (uptr)tab && addr < (uptr)(tab + kTableSizeL2)) {165int l2 = (addr - (uptr)tab) / sizeof(FdDesc);166FdDesc *d = &tab[l2];167*fd = l1 * kTableSizeL1 + l2;168*tid = d->creation_tid;169*stack = d->creation_stack;170*closed = d->closed;171return true;172}173}174return false;175}176177void FdAcquire(ThreadState *thr, uptr pc, int fd) {178if (bogusfd(fd))179return;180FdDesc *d = fddesc(thr, pc, fd);181FdSync *s = d->sync;182DPrintf("#%d: FdAcquire(%d) -> %p\n", thr->tid, fd, s);183MemoryAccess(thr, pc, (uptr)d, 8, kAccessRead);184if (s)185Acquire(thr, pc, (uptr)s);186}187188void FdRelease(ThreadState *thr, uptr pc, int fd) {189if (bogusfd(fd))190return;191FdDesc *d = fddesc(thr, pc, fd);192FdSync *s = d->sync;193DPrintf("#%d: FdRelease(%d) -> %p\n", thr->tid, fd, s);194MemoryAccess(thr, pc, (uptr)d, 8, kAccessRead);195if (s)196Release(thr, pc, (uptr)s);197if (uptr aux_sync = atomic_load(&d->aux_sync, memory_order_acquire))198Release(thr, pc, aux_sync);199}200201void FdAccess(ThreadState *thr, uptr pc, int fd) {202DPrintf("#%d: FdAccess(%d)\n", thr->tid, fd);203if (bogusfd(fd))204return;205FdDesc *d = fddesc(thr, pc, fd);206MemoryAccess(thr, pc, (uptr)d, 8, kAccessRead);207}208209void FdClose(ThreadState *thr, uptr pc, int fd, bool write) {210DPrintf("#%d: FdClose(%d)\n", thr->tid, fd);211if (bogusfd(fd))212return;213FdDesc *d = fddesc(thr, pc, fd);214{215// Need to lock the slot to make MemoryAccess and MemoryResetRange atomic216// with respect to global reset. See the comment in MemoryRangeFreed.217SlotLocker locker(thr);218if (!MustIgnoreInterceptor(thr)) {219if (write) {220// To catch races between fd usage and close.221MemoryAccess(thr, pc, (uptr)d, 8,222kAccessWrite | kAccessCheckOnly | kAccessSlotLocked);223} else {224// This path is used only by dup2/dup3 calls.225// We do read instead of write because there is a number of legitimate226// cases where write would lead to false positives:227// 1. Some software dups a closed pipe in place of a socket before228// closing229// the socket (to prevent races actually).230// 2. Some daemons dup /dev/null in place of stdin/stdout.231// On the other hand we have not seen cases when write here catches real232// bugs.233MemoryAccess(thr, pc, (uptr)d, 8,234kAccessRead | kAccessCheckOnly | kAccessSlotLocked);235}236}237// We need to clear it, because if we do not intercept any call out there238// that creates fd, we will hit false postives.239MemoryResetRange(thr, pc, (uptr)d, 8);240}241unref(thr, pc, d->sync);242d->sync = 0;243unref(thr, pc,244reinterpret_cast<FdSync *>(245atomic_load(&d->aux_sync, memory_order_relaxed)));246atomic_store(&d->aux_sync, 0, memory_order_relaxed);247d->closed = true;248d->creation_tid = thr->tid;249d->creation_stack = CurrentStackId(thr, pc);250}251252void FdFileCreate(ThreadState *thr, uptr pc, int fd) {253DPrintf("#%d: FdFileCreate(%d)\n", thr->tid, fd);254if (bogusfd(fd))255return;256init(thr, pc, fd, &fdctx.filesync);257}258259void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd, bool write) {260DPrintf("#%d: FdDup(%d, %d)\n", thr->tid, oldfd, newfd);261if (bogusfd(oldfd) || bogusfd(newfd))262return;263// Ignore the case when user dups not yet connected socket.264FdDesc *od = fddesc(thr, pc, oldfd);265MemoryAccess(thr, pc, (uptr)od, 8, kAccessRead);266FdClose(thr, pc, newfd, write);267init(thr, pc, newfd, ref(od->sync), write);268}269270void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd) {271DPrintf("#%d: FdCreatePipe(%d, %d)\n", thr->tid, rfd, wfd);272FdSync *s = allocsync(thr, pc);273init(thr, pc, rfd, ref(s));274init(thr, pc, wfd, ref(s));275unref(thr, pc, s);276}277278void FdEventCreate(ThreadState *thr, uptr pc, int fd) {279DPrintf("#%d: FdEventCreate(%d)\n", thr->tid, fd);280if (bogusfd(fd))281return;282init(thr, pc, fd, allocsync(thr, pc));283}284285void FdSignalCreate(ThreadState *thr, uptr pc, int fd) {286DPrintf("#%d: FdSignalCreate(%d)\n", thr->tid, fd);287if (bogusfd(fd))288return;289init(thr, pc, fd, 0);290}291292void FdInotifyCreate(ThreadState *thr, uptr pc, int fd) {293DPrintf("#%d: FdInotifyCreate(%d)\n", thr->tid, fd);294if (bogusfd(fd))295return;296init(thr, pc, fd, 0);297}298299void FdPollCreate(ThreadState *thr, uptr pc, int fd) {300DPrintf("#%d: FdPollCreate(%d)\n", thr->tid, fd);301if (bogusfd(fd))302return;303init(thr, pc, fd, allocsync(thr, pc));304}305306void FdPollAdd(ThreadState *thr, uptr pc, int epfd, int fd) {307DPrintf("#%d: FdPollAdd(%d, %d)\n", thr->tid, epfd, fd);308if (bogusfd(epfd) || bogusfd(fd))309return;310FdDesc *d = fddesc(thr, pc, fd);311// Associate fd with epoll fd only once.312// While an fd can be associated with multiple epolls at the same time,313// or with different epolls during different phases of lifetime,314// synchronization semantics (and examples) of this are unclear.315// So we don't support this for now.316// If we change the association, it will also create lifetime management317// problem for FdRelease which accesses the aux_sync.318if (atomic_load(&d->aux_sync, memory_order_relaxed))319return;320FdDesc *epd = fddesc(thr, pc, epfd);321FdSync *s = epd->sync;322if (!s)323return;324uptr cmp = 0;325if (atomic_compare_exchange_strong(326&d->aux_sync, &cmp, reinterpret_cast<uptr>(s), memory_order_release))327ref(s);328}329330void FdSocketCreate(ThreadState *thr, uptr pc, int fd) {331DPrintf("#%d: FdSocketCreate(%d)\n", thr->tid, fd);332if (bogusfd(fd))333return;334// It can be a UDP socket.335init(thr, pc, fd, &fdctx.socksync);336}337338void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd) {339DPrintf("#%d: FdSocketAccept(%d, %d)\n", thr->tid, fd, newfd);340if (bogusfd(fd))341return;342// Synchronize connect->accept.343Acquire(thr, pc, (uptr)&fdctx.connectsync);344init(thr, pc, newfd, &fdctx.socksync);345}346347void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) {348DPrintf("#%d: FdSocketConnecting(%d)\n", thr->tid, fd);349if (bogusfd(fd))350return;351// Synchronize connect->accept.352Release(thr, pc, (uptr)&fdctx.connectsync);353}354355void FdSocketConnect(ThreadState *thr, uptr pc, int fd) {356DPrintf("#%d: FdSocketConnect(%d)\n", thr->tid, fd);357if (bogusfd(fd))358return;359init(thr, pc, fd, &fdctx.socksync);360}361362uptr File2addr(const char *path) {363(void)path;364static u64 addr;365return (uptr)&addr;366}367368uptr Dir2addr(const char *path) {369(void)path;370static u64 addr;371return (uptr)&addr;372}373374} // namespace __tsan375376377