Introduce api to track fd ownership in libc.
Add two functions to allow objects that own a file descriptor to
enforce that only they can close their file descriptor.
Use them in FILE* and DIR*.
Bug: http://b/110100358
Test: bionic_unit_tests
Test: aosp/master boots without errors
Test: treehugger
Change-Id: Iecd6e8b26c62217271e0822dc3d2d7888b091a45
diff --git a/libc/private/bionic_fdsan.h b/libc/private/bionic_fdsan.h
new file mode 100644
index 0000000..b93260b
--- /dev/null
+++ b/libc/private/bionic_fdsan.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <android/fdsan.h>
+
+#include <errno.h>
+#include <stdatomic.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/user.h>
+
+#include <async_safe/log.h>
+
+struct FdEntry {
+ _Atomic(uint64_t) close_tag;
+};
+
+struct FdTableOverflow {
+ size_t len;
+ FdEntry entries[0];
+};
+
+template <size_t inline_fds>
+struct FdTable {
+ _Atomic(android_fdsan_error_level) error_level;
+
+ FdEntry entries[inline_fds];
+ _Atomic(FdTableOverflow*) overflow;
+
+ FdEntry* at(size_t idx) {
+ if (idx < inline_fds) {
+ return &entries[idx];
+ }
+
+ // Try to create the overflow table ourselves.
+ FdTableOverflow* local_overflow = atomic_load(&overflow);
+ if (__predict_false(!local_overflow)) {
+ struct rlimit rlim = { .rlim_max = 32768 };
+ getrlimit(RLIMIT_NOFILE, &rlim);
+ rlim_t max = rlim.rlim_max;
+
+ if (max == RLIM_INFINITY) {
+ // This isn't actually possible (the kernel has a hard limit), but just
+ // in case...
+ max = 32768;
+ }
+
+ if (idx > max) {
+ // This can happen if an fd is created and then the rlimit is lowered.
+ // In this case, just return nullptr and ignore the fd.
+ return nullptr;
+ }
+
+ size_t required_count = max - inline_fds;
+ size_t required_size = sizeof(FdTableOverflow) + required_count * sizeof(FdEntry);
+ size_t aligned_size = __BIONIC_ALIGN(required_size, PAGE_SIZE);
+ size_t aligned_count = (aligned_size - sizeof(FdTableOverflow)) / sizeof(FdEntry);
+
+ void* allocation =
+ mmap(nullptr, aligned_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (allocation == MAP_FAILED) {
+ async_safe_fatal("fdsan: mmap failed: %s", strerror(errno));
+ }
+
+ FdTableOverflow* new_overflow = reinterpret_cast<FdTableOverflow*>(allocation);
+ new_overflow->len = aligned_count;
+
+ if (atomic_compare_exchange_strong(&overflow, &local_overflow, new_overflow)) {
+ local_overflow = new_overflow;
+ } else {
+ // Someone beat us to it. Deallocate and use theirs.
+ munmap(allocation, aligned_size);
+ }
+ }
+
+ size_t offset = idx - inline_fds;
+ if (local_overflow->len < offset) {
+ return nullptr;
+ }
+ return &local_overflow->entries[offset];
+ }
+};