1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "partition_alloc/partition_alloc_base/rand_util.h"
6 
7 #include <fcntl.h>
8 #include <sys/syscall.h>
9 #include <unistd.h>
10 
11 #include <cerrno>
12 #include <cstddef>
13 #include <cstdint>
14 #include <sstream>
15 
16 #include "build/build_config.h"
17 #include "partition_alloc/partition_alloc_base/check.h"
18 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
19 #include "partition_alloc/partition_alloc_base/files/file_util.h"
20 #include "partition_alloc/partition_alloc_base/no_destructor.h"
21 #include "partition_alloc/partition_alloc_base/posix/eintr_wrapper.h"
22 
23 #if BUILDFLAG(IS_MAC)
24 // TODO(crbug.com/995996): Waiting for this header to appear in the iOS SDK.
25 // (See below.)
26 #include <sys/random.h>
27 #endif
28 
29 namespace {
30 
31 #if BUILDFLAG(IS_AIX)
32 // AIX has no 64-bit support for O_CLOEXEC.
33 static constexpr int kOpenFlags = O_RDONLY;
34 #else
35 static constexpr int kOpenFlags = O_RDONLY | O_CLOEXEC;
36 #endif
37 
38 // On Android the 'open' function has two versions:
39 // int open(const char *pathname, int flags);
40 // int open(const char *pathname, int flags, mode_t mode);
41 //
42 // This doesn't play well with WrapEINTR template. This alias helps the compiler
43 // to make a decision.
OpenFile(const char * pathname,int flags)44 int OpenFile(const char* pathname, int flags) {
45   return open(pathname, flags);
46 }
47 
48 // We keep the file descriptor for /dev/urandom around so we don't need to
49 // reopen it (which is expensive), and since we may not even be able to reopen
50 // it if we are later put in a sandbox. This class wraps the file descriptor so
51 // we can use a static-local variable to handle opening it on the first access.
52 class URandomFd {
53  public:
URandomFd()54   URandomFd()
55       : fd_(partition_alloc::WrapEINTR(OpenFile)("/dev/urandom", kOpenFlags)) {
56     PA_BASE_CHECK(fd_ >= 0) << "Cannot open /dev/urandom";
57   }
58 
~URandomFd()59   ~URandomFd() { close(fd_); }
60 
fd() const61   int fd() const { return fd_; }
62 
63  private:
64   const int fd_;
65 };
66 
GetUrandomFD()67 int GetUrandomFD() {
68   static partition_alloc::internal::base::NoDestructor<URandomFd> urandom_fd;
69   return urandom_fd->fd();
70 }
71 
72 }  // namespace
73 
74 namespace partition_alloc::internal::base {
75 
76 // NOTE: In an ideal future, all implementations of this function will just
77 // wrap BoringSSL's `RAND_bytes`. TODO(crbug.com/995996): Figure out the
78 // build/test/performance issues with dcheng's CL
79 // (https://chromium-review.googlesource.com/c/chromium/src/+/1545096) and land
80 // it or some form of it.
RandBytes(void * output,size_t output_length)81 void RandBytes(void* output, size_t output_length) {
82 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
83   // Use `syscall(__NR_getrandom...` to avoid a dependency on
84   // `third_party/linux_syscall_support.h`.
85   //
86   // Here in PartitionAlloc, we don't need to look before we leap
87   // because we know that both Linux and CrOS only support kernels
88   // that do have this syscall defined. This diverges from upstream
89   // `//base` behavior both here and below.
90   const ssize_t r =
91       WrapEINTR(syscall)(__NR_getrandom, output, output_length, 0);
92 
93   // Return success only on total success. In case errno == ENOSYS (or any other
94   // error), we'll fall through to reading from urandom below.
95   if (output_length == static_cast<size_t>(r)) {
96     PA_MSAN_UNPOISON(output, output_length);
97     return;
98   }
99 #elif BUILDFLAG(IS_MAC)
100   // TODO(crbug.com/995996): Enable this on iOS too, when sys/random.h arrives
101   // in its SDK.
102   if (getentropy(output, output_length) == 0) {
103     return;
104   }
105 #endif
106   // If getrandom(2) above returned with an error and the /dev/urandom fallback
107   // took place on Linux/ChromeOS bots, they would fail with a CHECK in
108   // nacl_helper. The latter assumes that the number of open file descriptors
109   // must be constant. The nacl_helper knows about the FD from
110   // //base/rand_utils, but is not aware of the urandom_fd from this file (see
111   // CheckForExpectedNumberOfOpenFds).
112   //
113   // *  On `linux_chromium_asan_rel_ng` in
114   //    `ContentBrowserTest.RendererCrashCallStack`:
115   //    ```
116   //    [FATAL:rand_util_posix.cc(45)] Check failed: fd_ >= 0. Cannot open
117   //    /dev/urandom
118   //    ```
119   // *  On `linux-lacros-rel` in
120   //    `NaClBrowserTestGLibc.CrashInCallback`:
121   //    ```
122   //    2023-07-03T11:31:13.115755Z FATAL nacl_helper:
123   //    [nacl_sandbox_linux.cc(178)] Check failed: expected_num_fds ==
124   //    sandbox::ProcUtil::CountOpenFds(proc_fd_.get()) (6 vs. 7)
125   //    ```
126   const int urandom_fd = GetUrandomFD();
127   const bool success =
128       ReadFromFD(urandom_fd, static_cast<char*>(output), output_length);
129   PA_BASE_CHECK(success);
130 }
131 
132 }  // namespace partition_alloc::internal::base
133