| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <dirent.h> |
| #include <map> |
| |
| #include "debug.h" |
| #include "sandbox_impl.h" |
| #include "system_call_table.h" |
| |
| namespace playground { |
| |
| struct SandboxPolicy g_policy; |
| |
| struct Thread { |
| int fdPub, fd; |
| SecureMem::Args* mem; |
| }; |
| |
| SecureMem::Args* Sandbox::getNewSecureMem() { |
| if (!secureMemPool_.empty()) { |
| SecureMem::Args* rc = secureMemPool_.back(); |
| secureMemPool_.pop_back(); |
| memset(rc->scratchPage, 0, sizeof(rc->scratchPage)); |
| return rc; |
| } |
| return NULL; |
| } |
| |
| void Sandbox::trustedProcess(int parentMapsFd, int processFdPub, int sandboxFd, |
| int cloneFd, SecureMem::Args* secureArena) { |
| // The trusted process doesn't have access to our custom TLS. |
| // Disable debugging because that relies on our custom TLS. |
| Debug::disable(); |
| |
| std::map<long long, struct Thread> threads; |
| SysCalls sys; |
| long long cookie = 0; |
| |
| // The very first entry in the secure memory arena has been assigned to the |
| // initial thread. The remaining entries are available for allocation. |
| SecureMem::Args* startAddress = secureArena; |
| SecureMem::Args* nextThread = startAddress; |
| for (int i = 0; i < kMaxThreads-1; i++) { |
| secureMemPool_.push_back(++startAddress); |
| } |
| |
| newThreadCreated: |
| // Receive information from newly created thread |
| Thread *newThread = &threads[++cookie]; |
| memset(newThread, 0, sizeof(Thread)); |
| struct { |
| SecureMem::Args* self; |
| int tid; |
| int fdPub; |
| } __attribute__((packed)) data; |
| |
| size_t dataLen = sizeof(data); |
| if (!getFd(cloneFd, &newThread->fdPub, &newThread->fd, &data, &dataLen) || |
| dataLen != sizeof(data)) { |
| // We get here either because the sandbox got corrupted, or because our |
| // parent process has terminated. |
| if (newThread->fdPub || dataLen) { |
| die("Failed to receive new thread information"); |
| } |
| die(); |
| } |
| if (data.self != nextThread) { |
| // The only potentially security critical information received from the |
| // newly created thread is "self". The "tid" is for informational purposes |
| // (and for use in the new thread's TLS), and "fdPub" is uncritical as all |
| // file descriptors are considered untrusted. |
| // Thus, we only use "self" for a sanity check, but don't actually trust |
| // it beyond that. |
| die("Received corrupted thread information"); |
| } |
| newThread->mem = nextThread; |
| |
| // Set up TLS area and let thread know that the data is now ready |
| nextThread->cookie = cookie; |
| nextThread->threadId = data.tid; |
| nextThread->threadFdPub = data.fdPub; |
| write(sys, newThread->fd, "", 1); |
| |
| // Dispatch system calls that have been forwarded from the trusted thread(s). |
| for (;;) { |
| struct RequestHeader header; |
| int rc; |
| if ((rc = read(sys, sandboxFd, &header, sizeof(header))) !=sizeof(header)){ |
| if (rc) { |
| die("Failed to read system call number and thread id"); |
| } |
| die(); |
| } |
| std::map<long long, struct Thread>::iterator iter = |
| threads.find(header.cookie); |
| if (iter == threads.end()) { |
| die("Received request from unknown thread"); |
| } |
| struct Thread* currentThread = &iter->second; |
| if (header.sysnum > SyscallTable::maxSyscall || |
| !SyscallTable::syscallTable[header.sysnum].trustedProcess) { |
| die("Trusted process encountered unexpected system call"); |
| } |
| |
| // Dispatch system call to handler function. Treat both exit() and clone() |
| // specially. |
| SyscallRequestInfo info = { 0 }; |
| info.sysnum = header.sysnum; |
| info.mem = currentThread->mem; |
| info.trustedProcessFd = sandboxFd; |
| info.trustedThreadFd = currentThread->fdPub; |
| info.applicationFd = currentThread->fd; |
| info.parentMapsFd = parentMapsFd; |
| if (SyscallTable::syscallTable[header.sysnum].trustedProcess(&info) && |
| header.sysnum == __NR_clone) { |
| nextThread = currentThread->mem->newSecureMem; |
| goto newThreadCreated; |
| } else if (header.sysnum == __NR_exit) { |
| (void)NOINTR_SYS(sys.close(iter->second.fdPub)); |
| (void)NOINTR_SYS(sys.close(iter->second.fd)); |
| SecureMem::Args* secureMem = currentThread->mem; |
| threads.erase(iter); |
| secureMemPool_.push_back(secureMem); |
| } |
| } |
| } |
| |
| int Sandbox::initializeProtectedMap(int fd) { |
| int mapsFd; |
| if (!getFd(fd, &mapsFd, NULL, NULL, NULL)) { |
| maps_failure: |
| die("Cannot access /proc/self/maps"); |
| } |
| |
| // Read the memory mappings as they were before the sandbox takes effect. |
| // These mappings cannot be changed by the sandboxed process. |
| char line[80]; |
| FILE *fp = fdopen(mapsFd, "r"); |
| for (bool truncated = false;;) { |
| if (fgets(line, sizeof(line), fp) == NULL) { |
| if (feof(fp) || errno != EINTR) { |
| break; |
| } |
| continue; |
| } |
| if (!truncated) { |
| unsigned long start, stop; |
| char *ptr = line; |
| errno = 0; |
| start = strtoul(ptr, &ptr, 16); |
| if (errno || *ptr++ != '-') { |
| parse_failure: |
| die("Failed to parse /proc/self/maps"); |
| } |
| stop = strtoul(ptr, &ptr, 16); |
| if (errno || *ptr++ != ' ') { |
| goto parse_failure; |
| } |
| protectedMap_[reinterpret_cast<void *>(start)] = stop - start; |
| } |
| truncated = strchr(line, '\n') == NULL; |
| } |
| |
| // Prevent low address memory allocations. Some buggy kernels allow those |
| if (protectedMap_[0] < (64 << 10)) { |
| protectedMap_[0] = 64 << 10; |
| } |
| |
| // Let the sandbox know that we are done parsing the memory map. |
| SysCalls sys; |
| if (write(sys, fd, &mapsFd, sizeof(mapsFd)) != sizeof(mapsFd)) { |
| goto maps_failure; |
| } |
| |
| return mapsFd; |
| } |
| |
| bool Sandbox::isRegionProtected(void *addr, size_t size) { |
| void *stop = (char *) addr + size; |
| ProtectedMap::const_iterator iter = protectedMap_.lower_bound(addr); |
| if (iter != protectedMap_.begin()) { |
| --iter; |
| } |
| for (; iter != protectedMap_.end() && iter->first < stop; ++iter) { |
| if (addr < (char *) iter->first + iter->second && |
| stop > iter->first) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| SecureMem::Args* Sandbox::createTrustedProcess(int processFdPub, int sandboxFd, |
| int cloneFdPub, int cloneFd) { |
| // Allocate memory that will be used by an arena for storing the secure |
| // memory. While we allow this memory area to be empty at times (e.g. when |
| // not all threads are in use), we make sure that it never gets overwritten |
| // by user-allocated memory. This happens in initializeProtectedMap() and |
| // snapshotMemoryMappings(). |
| SecureMem::Args* secureArena = reinterpret_cast<SecureMem::Args*>( |
| mmap(NULL, 8192*kMaxThreads, PROT_READ|PROT_WRITE, |
| MAP_SHARED|MAP_ANONYMOUS, -1, 0)); |
| if (secureArena == MAP_FAILED) { |
| die("Failed to allocate secure memory arena"); |
| } |
| |
| // Create a trusted process that can evaluate system call parameters and |
| // decide whether a system call should execute. This process runs outside of |
| // the seccomp sandbox. It communicates with the sandbox'd process through |
| // a socketpair() and through securely shared memory. |
| pid_t pid = fork(); |
| if (pid < 0) { |
| die("Failed to create trusted process"); |
| } |
| if (!pid) { |
| // Close all file handles except for sandboxFd, cloneFd, and stdio |
| DIR *dir = opendir("/proc/self/fd"); |
| if (dir == 0) { |
| // If we don't know the list of our open file handles, just try closing |
| // all valid ones. |
| for (int fd = sysconf(_SC_OPEN_MAX); --fd > 2; ) { |
| if (fd != sandboxFd && fd != cloneFd) { |
| close(fd); |
| } |
| } |
| } else { |
| // If available, if is much more efficient to just close the file |
| // handles that show up in /proc/self/fd/ |
| struct dirent de, *res; |
| while (!readdir_r(dir, &de, &res) && res) { |
| if (res->d_name[0] < '0') |
| continue; |
| int fd = atoi(res->d_name); |
| if (fd > 2 && |
| fd != sandboxFd && fd != cloneFd && fd != dirfd(dir)) { |
| close(fd); |
| } |
| } |
| closedir(dir); |
| } |
| |
| // Initialize secure memory used for threads |
| for (int i = 0; i < kMaxThreads; i++) { |
| SecureMem::Args* args = secureArena + i; |
| args->self = args; |
| #ifndef NDEBUG |
| args->allowAllSystemCalls= Debug::isEnabled(); |
| #endif |
| args->maxSyscall = SyscallTable::maxSyscall; |
| args->syscallTable = SyscallTable::syscallTable; |
| } |
| |
| int parentMapsFd = initializeProtectedMap(sandboxFd); |
| trustedProcess(parentMapsFd, processFdPub, sandboxFd, |
| cloneFd, secureArena); |
| die(); |
| } |
| |
| // We are still in the untrusted code. Deny access to restricted resources. |
| mprotect(secureArena, 8192*kMaxThreads, PROT_NONE); |
| close(sandboxFd); |
| |
| return secureArena; |
| } |
| |
| } // namespace |