Home | History | Annotate | Download | only in services
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      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 "sandbox/linux/services/broker_process.h"
      6 
      7 #include <errno.h>
      8 #include <fcntl.h>
      9 #include <sys/resource.h>
     10 #include <sys/stat.h>
     11 #include <sys/types.h>
     12 #include <sys/wait.h>
     13 #include <unistd.h>
     14 
     15 #include <algorithm>
     16 #include <string>
     17 #include <vector>
     18 
     19 #include "base/basictypes.h"
     20 #include "base/bind.h"
     21 #include "base/files/file_util.h"
     22 #include "base/files/scoped_file.h"
     23 #include "base/logging.h"
     24 #include "base/memory/scoped_ptr.h"
     25 #include "base/posix/eintr_wrapper.h"
     26 #include "base/posix/unix_domain_socket_linux.h"
     27 #include "sandbox/linux/tests/scoped_temporary_file.h"
     28 #include "sandbox/linux/tests/test_utils.h"
     29 #include "sandbox/linux/tests/unit_tests.h"
     30 #include "testing/gtest/include/gtest/gtest.h"
     31 
     32 namespace sandbox {
     33 
     34 class BrokerProcessTestHelper {
     35  public:
     36   static int get_ipc_socketpair(const BrokerProcess* broker) {
     37     return broker->ipc_socketpair_;
     38   }
     39 };
     40 
     41 namespace {
     42 
     43 bool NoOpCallback() { return true; }
     44 
     45 }  // namespace
     46 
     47 TEST(BrokerProcess, CreateAndDestroy) {
     48   std::vector<std::string> read_whitelist;
     49   read_whitelist.push_back("/proc/cpuinfo");
     50 
     51   scoped_ptr<BrokerProcess> open_broker(
     52       new BrokerProcess(EPERM, read_whitelist, std::vector<std::string>()));
     53   ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback)));
     54 
     55   ASSERT_TRUE(TestUtils::CurrentProcessHasChildren());
     56   // Destroy the broker and check it has exited properly.
     57   open_broker.reset();
     58   ASSERT_FALSE(TestUtils::CurrentProcessHasChildren());
     59 }
     60 
     61 TEST(BrokerProcess, TestOpenAccessNull) {
     62   const std::vector<std::string> empty;
     63   BrokerProcess open_broker(EPERM, empty, empty);
     64   ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback)));
     65 
     66   int fd = open_broker.Open(NULL, O_RDONLY);
     67   ASSERT_EQ(fd, -EFAULT);
     68 
     69   int ret = open_broker.Access(NULL, F_OK);
     70   ASSERT_EQ(ret, -EFAULT);
     71 }
     72 
     73 void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) {
     74   const char kR_WhiteListed[] = "/proc/DOESNOTEXIST1";
     75   // We can't debug the init process, and shouldn't be able to access
     76   // its auxv file.
     77   const char kR_WhiteListedButDenied[] = "/proc/1/auxv";
     78   const char kW_WhiteListed[] = "/proc/DOESNOTEXIST2";
     79   const char kRW_WhiteListed[] = "/proc/DOESNOTEXIST3";
     80   const char k_NotWhitelisted[] = "/proc/DOESNOTEXIST4";
     81 
     82   std::vector<std::string> read_whitelist;
     83   read_whitelist.push_back(kR_WhiteListed);
     84   read_whitelist.push_back(kR_WhiteListedButDenied);
     85   read_whitelist.push_back(kRW_WhiteListed);
     86 
     87   std::vector<std::string> write_whitelist;
     88   write_whitelist.push_back(kW_WhiteListed);
     89   write_whitelist.push_back(kRW_WhiteListed);
     90 
     91   BrokerProcess open_broker(denied_errno,
     92                             read_whitelist,
     93                             write_whitelist,
     94                             fast_check_in_client);
     95   ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback)));
     96 
     97   int fd = -1;
     98   fd = open_broker.Open(kR_WhiteListed, O_RDONLY);
     99   ASSERT_EQ(fd, -ENOENT);
    100   fd = open_broker.Open(kR_WhiteListed, O_WRONLY);
    101   ASSERT_EQ(fd, -denied_errno);
    102   fd = open_broker.Open(kR_WhiteListed, O_RDWR);
    103   ASSERT_EQ(fd, -denied_errno);
    104   int ret = -1;
    105   ret = open_broker.Access(kR_WhiteListed, F_OK);
    106   ASSERT_EQ(ret, -ENOENT);
    107   ret = open_broker.Access(kR_WhiteListed, R_OK);
    108   ASSERT_EQ(ret, -ENOENT);
    109   ret = open_broker.Access(kR_WhiteListed, W_OK);
    110   ASSERT_EQ(ret, -denied_errno);
    111   ret = open_broker.Access(kR_WhiteListed, R_OK | W_OK);
    112   ASSERT_EQ(ret, -denied_errno);
    113   ret = open_broker.Access(kR_WhiteListed, X_OK);
    114   ASSERT_EQ(ret, -denied_errno);
    115   ret = open_broker.Access(kR_WhiteListed, R_OK | X_OK);
    116   ASSERT_EQ(ret, -denied_errno);
    117 
    118   // Android sometimes runs tests as root.
    119   // This part of the test requires a process that doesn't have
    120   // CAP_DAC_OVERRIDE. We check against a root euid as a proxy for that.
    121   if (geteuid()) {
    122     fd = open_broker.Open(kR_WhiteListedButDenied, O_RDONLY);
    123     // The broker process will allow this, but the normal permission system
    124     // won't.
    125     ASSERT_EQ(fd, -EACCES);
    126     fd = open_broker.Open(kR_WhiteListedButDenied, O_WRONLY);
    127     ASSERT_EQ(fd, -denied_errno);
    128     fd = open_broker.Open(kR_WhiteListedButDenied, O_RDWR);
    129     ASSERT_EQ(fd, -denied_errno);
    130     ret = open_broker.Access(kR_WhiteListedButDenied, F_OK);
    131     // The normal permission system will let us check that the file exists.
    132     ASSERT_EQ(ret, 0);
    133     ret = open_broker.Access(kR_WhiteListedButDenied, R_OK);
    134     ASSERT_EQ(ret, -EACCES);
    135     ret = open_broker.Access(kR_WhiteListedButDenied, W_OK);
    136     ASSERT_EQ(ret, -denied_errno);
    137     ret = open_broker.Access(kR_WhiteListedButDenied, R_OK | W_OK);
    138     ASSERT_EQ(ret, -denied_errno);
    139     ret = open_broker.Access(kR_WhiteListedButDenied, X_OK);
    140     ASSERT_EQ(ret, -denied_errno);
    141     ret = open_broker.Access(kR_WhiteListedButDenied, R_OK | X_OK);
    142     ASSERT_EQ(ret, -denied_errno);
    143   }
    144 
    145   fd = open_broker.Open(kW_WhiteListed, O_RDONLY);
    146   ASSERT_EQ(fd, -denied_errno);
    147   fd = open_broker.Open(kW_WhiteListed, O_WRONLY);
    148   ASSERT_EQ(fd, -ENOENT);
    149   fd = open_broker.Open(kW_WhiteListed, O_RDWR);
    150   ASSERT_EQ(fd, -denied_errno);
    151   ret = open_broker.Access(kW_WhiteListed, F_OK);
    152   ASSERT_EQ(ret, -ENOENT);
    153   ret = open_broker.Access(kW_WhiteListed, R_OK);
    154   ASSERT_EQ(ret, -denied_errno);
    155   ret = open_broker.Access(kW_WhiteListed, W_OK);
    156   ASSERT_EQ(ret, -ENOENT);
    157   ret = open_broker.Access(kW_WhiteListed, R_OK | W_OK);
    158   ASSERT_EQ(ret, -denied_errno);
    159   ret = open_broker.Access(kW_WhiteListed, X_OK);
    160   ASSERT_EQ(ret, -denied_errno);
    161   ret = open_broker.Access(kW_WhiteListed, R_OK | X_OK);
    162   ASSERT_EQ(ret, -denied_errno);
    163 
    164   fd = open_broker.Open(kRW_WhiteListed, O_RDONLY);
    165   ASSERT_EQ(fd, -ENOENT);
    166   fd = open_broker.Open(kRW_WhiteListed, O_WRONLY);
    167   ASSERT_EQ(fd, -ENOENT);
    168   fd = open_broker.Open(kRW_WhiteListed, O_RDWR);
    169   ASSERT_EQ(fd, -ENOENT);
    170   ret = open_broker.Access(kRW_WhiteListed, F_OK);
    171   ASSERT_EQ(ret, -ENOENT);
    172   ret = open_broker.Access(kRW_WhiteListed, R_OK);
    173   ASSERT_EQ(ret, -ENOENT);
    174   ret = open_broker.Access(kRW_WhiteListed, W_OK);
    175   ASSERT_EQ(ret, -ENOENT);
    176   ret = open_broker.Access(kRW_WhiteListed, R_OK | W_OK);
    177   ASSERT_EQ(ret, -ENOENT);
    178   ret = open_broker.Access(kRW_WhiteListed, X_OK);
    179   ASSERT_EQ(ret, -denied_errno);
    180   ret = open_broker.Access(kRW_WhiteListed, R_OK | X_OK);
    181   ASSERT_EQ(ret, -denied_errno);
    182 
    183   fd = open_broker.Open(k_NotWhitelisted, O_RDONLY);
    184   ASSERT_EQ(fd, -denied_errno);
    185   fd = open_broker.Open(k_NotWhitelisted, O_WRONLY);
    186   ASSERT_EQ(fd, -denied_errno);
    187   fd = open_broker.Open(k_NotWhitelisted, O_RDWR);
    188   ASSERT_EQ(fd, -denied_errno);
    189   ret = open_broker.Access(k_NotWhitelisted, F_OK);
    190   ASSERT_EQ(ret, -denied_errno);
    191   ret = open_broker.Access(k_NotWhitelisted, R_OK);
    192   ASSERT_EQ(ret, -denied_errno);
    193   ret = open_broker.Access(k_NotWhitelisted, W_OK);
    194   ASSERT_EQ(ret, -denied_errno);
    195   ret = open_broker.Access(k_NotWhitelisted, R_OK | W_OK);
    196   ASSERT_EQ(ret, -denied_errno);
    197   ret = open_broker.Access(k_NotWhitelisted, X_OK);
    198   ASSERT_EQ(ret, -denied_errno);
    199   ret = open_broker.Access(k_NotWhitelisted, R_OK | X_OK);
    200   ASSERT_EQ(ret, -denied_errno);
    201 
    202   // We have some extra sanity check for clearly wrong values.
    203   fd = open_broker.Open(kRW_WhiteListed, O_RDONLY | O_WRONLY | O_RDWR);
    204   ASSERT_EQ(fd, -denied_errno);
    205 
    206   // It makes no sense to allow O_CREAT in a 2-parameters open. Ensure this
    207   // is denied.
    208   fd = open_broker.Open(kRW_WhiteListed, O_RDWR | O_CREAT);
    209   ASSERT_EQ(fd, -denied_errno);
    210 }
    211 
    212 // Run the same thing twice. The second time, we make sure that no security
    213 // check is performed on the client.
    214 TEST(BrokerProcess, OpenFilePermsWithClientCheck) {
    215   TestOpenFilePerms(true /* fast_check_in_client */, EPERM);
    216   // Don't do anything here, so that ASSERT works in the subfunction as
    217   // expected.
    218 }
    219 
    220 TEST(BrokerProcess, OpenOpenFilePermsNoClientCheck) {
    221   TestOpenFilePerms(false /* fast_check_in_client */, EPERM);
    222   // Don't do anything here, so that ASSERT works in the subfunction as
    223   // expected.
    224 }
    225 
    226 // Run the same twice again, but with ENOENT instead of EPERM.
    227 TEST(BrokerProcess, OpenFilePermsWithClientCheckNoEnt) {
    228   TestOpenFilePerms(true /* fast_check_in_client */, ENOENT);
    229   // Don't do anything here, so that ASSERT works in the subfunction as
    230   // expected.
    231 }
    232 
    233 TEST(BrokerProcess, OpenOpenFilePermsNoClientCheckNoEnt) {
    234   TestOpenFilePerms(false /* fast_check_in_client */, ENOENT);
    235   // Don't do anything here, so that ASSERT works in the subfunction as
    236   // expected.
    237 }
    238 
    239 void TestOpenCpuinfo(bool fast_check_in_client) {
    240   const char kFileCpuInfo[] = "/proc/cpuinfo";
    241   std::vector<std::string> read_whitelist;
    242   read_whitelist.push_back(kFileCpuInfo);
    243 
    244   scoped_ptr<BrokerProcess> open_broker(new BrokerProcess(
    245       EPERM, read_whitelist, std::vector<std::string>(), fast_check_in_client));
    246   ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback)));
    247 
    248   int fd = -1;
    249   fd = open_broker->Open(kFileCpuInfo, O_RDWR);
    250   base::ScopedFD fd_closer(fd);
    251   ASSERT_EQ(fd, -EPERM);
    252 
    253   // Check we can read /proc/cpuinfo.
    254   int can_access = open_broker->Access(kFileCpuInfo, R_OK);
    255   ASSERT_EQ(can_access, 0);
    256   can_access = open_broker->Access(kFileCpuInfo, W_OK);
    257   ASSERT_EQ(can_access, -EPERM);
    258   // Check we can not write /proc/cpuinfo.
    259 
    260   // Open cpuinfo via the broker.
    261   int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY);
    262   base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd);
    263   ASSERT_GE(cpuinfo_fd, 0);
    264   char buf[3];
    265   memset(buf, 0, sizeof(buf));
    266   int read_len1 = read(cpuinfo_fd, buf, sizeof(buf));
    267   ASSERT_GT(read_len1, 0);
    268 
    269   // Open cpuinfo directly.
    270   int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY);
    271   base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2);
    272   ASSERT_GE(cpuinfo_fd2, 0);
    273   char buf2[3];
    274   memset(buf2, 1, sizeof(buf2));
    275   int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2));
    276   ASSERT_GT(read_len1, 0);
    277 
    278   // The following is not guaranteed true, but will be in practice.
    279   ASSERT_EQ(read_len1, read_len2);
    280   // Compare the cpuinfo as returned by the broker with the one we opened
    281   // ourselves.
    282   ASSERT_EQ(memcmp(buf, buf2, read_len1), 0);
    283 
    284   ASSERT_TRUE(TestUtils::CurrentProcessHasChildren());
    285   open_broker.reset();
    286   ASSERT_FALSE(TestUtils::CurrentProcessHasChildren());
    287 }
    288 
    289 // Run the same thing twice. The second time, we make sure that no security
    290 // check is performed on the client.
    291 TEST(BrokerProcess, OpenCpuinfoWithClientCheck) {
    292   TestOpenCpuinfo(true /* fast_check_in_client */);
    293   // Don't do anything here, so that ASSERT works in the subfunction as
    294   // expected.
    295 }
    296 
    297 TEST(BrokerProcess, OpenCpuinfoNoClientCheck) {
    298   TestOpenCpuinfo(false /* fast_check_in_client */);
    299   // Don't do anything here, so that ASSERT works in the subfunction as
    300   // expected.
    301 }
    302 
    303 TEST(BrokerProcess, OpenFileRW) {
    304   ScopedTemporaryFile tempfile;
    305   const char* tempfile_name = tempfile.full_file_name();
    306 
    307   std::vector<std::string> whitelist;
    308   whitelist.push_back(tempfile_name);
    309 
    310   BrokerProcess open_broker(EPERM, whitelist, whitelist);
    311   ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback)));
    312 
    313   // Check we can access that file with read or write.
    314   int can_access = open_broker.Access(tempfile_name, R_OK | W_OK);
    315   ASSERT_EQ(can_access, 0);
    316 
    317   int tempfile2 = -1;
    318   tempfile2 = open_broker.Open(tempfile_name, O_RDWR);
    319   ASSERT_GE(tempfile2, 0);
    320 
    321   // Write to the descriptor opened by the broker.
    322   char test_text[] = "TESTTESTTEST";
    323   ssize_t len = write(tempfile2, test_text, sizeof(test_text));
    324   ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text)));
    325 
    326   // Read back from the original file descriptor what we wrote through
    327   // the descriptor provided by the broker.
    328   char buf[1024];
    329   len = read(tempfile.fd(), buf, sizeof(buf));
    330 
    331   ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text)));
    332   ASSERT_EQ(memcmp(test_text, buf, sizeof(test_text)), 0);
    333 
    334   ASSERT_EQ(close(tempfile2), 0);
    335 }
    336 
    337 // SANDBOX_TEST because the process could die with a SIGPIPE
    338 // and we want this to happen in a subprocess.
    339 SANDBOX_TEST(BrokerProcess, BrokerDied) {
    340   std::vector<std::string> read_whitelist;
    341   read_whitelist.push_back("/proc/cpuinfo");
    342 
    343   BrokerProcess open_broker(EPERM,
    344                             read_whitelist,
    345                             std::vector<std::string>(),
    346                             true /* fast_check_in_client */,
    347                             true /* quiet_failures_for_tests */);
    348   SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback)));
    349   const pid_t broker_pid = open_broker.broker_pid();
    350   SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0);
    351 
    352   // Now we check that the broker has been signaled, but do not reap it.
    353   siginfo_t process_info;
    354   SANDBOX_ASSERT(HANDLE_EINTR(waitid(
    355                      P_PID, broker_pid, &process_info, WEXITED | WNOWAIT)) ==
    356                  0);
    357   SANDBOX_ASSERT(broker_pid == process_info.si_pid);
    358   SANDBOX_ASSERT(CLD_KILLED == process_info.si_code);
    359   SANDBOX_ASSERT(SIGKILL == process_info.si_status);
    360 
    361   // Check that doing Open with a dead broker won't SIGPIPE us.
    362   SANDBOX_ASSERT(open_broker.Open("/proc/cpuinfo", O_RDONLY) == -ENOMEM);
    363   SANDBOX_ASSERT(open_broker.Access("/proc/cpuinfo", O_RDONLY) == -ENOMEM);
    364 }
    365 
    366 void TestOpenComplexFlags(bool fast_check_in_client) {
    367   const char kCpuInfo[] = "/proc/cpuinfo";
    368   std::vector<std::string> whitelist;
    369   whitelist.push_back(kCpuInfo);
    370 
    371   BrokerProcess open_broker(EPERM,
    372                             whitelist,
    373                             whitelist,
    374                             fast_check_in_client);
    375   ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback)));
    376   // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK.
    377   int fd = -1;
    378   int ret = 0;
    379   fd = open_broker.Open(kCpuInfo, O_RDONLY);
    380   ASSERT_GE(fd, 0);
    381   ret = fcntl(fd, F_GETFL);
    382   ASSERT_NE(-1, ret);
    383   // The descriptor shouldn't have the O_CLOEXEC attribute, nor O_NONBLOCK.
    384   ASSERT_EQ(0, ret & (O_CLOEXEC | O_NONBLOCK));
    385   ASSERT_EQ(0, close(fd));
    386 
    387   fd = open_broker.Open(kCpuInfo, O_RDONLY | O_CLOEXEC);
    388   ASSERT_GE(fd, 0);
    389   ret = fcntl(fd, F_GETFD);
    390   ASSERT_NE(-1, ret);
    391   // Important: use F_GETFD, not F_GETFL. The O_CLOEXEC flag in F_GETFL
    392   // is actually not used by the kernel.
    393   ASSERT_TRUE(FD_CLOEXEC & ret);
    394   ASSERT_EQ(0, close(fd));
    395 
    396   fd = open_broker.Open(kCpuInfo, O_RDONLY | O_NONBLOCK);
    397   ASSERT_GE(fd, 0);
    398   ret = fcntl(fd, F_GETFL);
    399   ASSERT_NE(-1, ret);
    400   ASSERT_TRUE(O_NONBLOCK & ret);
    401   ASSERT_EQ(0, close(fd));
    402 }
    403 
    404 TEST(BrokerProcess, OpenComplexFlagsWithClientCheck) {
    405   TestOpenComplexFlags(true /* fast_check_in_client */);
    406   // Don't do anything here, so that ASSERT works in the subfunction as
    407   // expected.
    408 }
    409 
    410 TEST(BrokerProcess, OpenComplexFlagsNoClientCheck) {
    411   TestOpenComplexFlags(false /* fast_check_in_client */);
    412   // Don't do anything here, so that ASSERT works in the subfunction as
    413   // expected.
    414 }
    415 
    416 // We need to allow noise because the broker will log when it receives our
    417 // bogus IPCs.
    418 SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, RecvMsgDescriptorLeak) {
    419   // Android creates a socket on first use of the LOG call.
    420   // We need to ensure this socket is open before we
    421   // begin the test.
    422   LOG(INFO) << "Ensure Android LOG socket is allocated";
    423 
    424   // Find the four lowest available file descriptors.
    425   int available_fds[4];
    426   SANDBOX_ASSERT(0 == pipe(available_fds));
    427   SANDBOX_ASSERT(0 == pipe(available_fds + 2));
    428 
    429   // Save one FD to send to the broker later, and close the others.
    430   base::ScopedFD message_fd(available_fds[0]);
    431   for (size_t i = 1; i < arraysize(available_fds); i++) {
    432     SANDBOX_ASSERT(0 == IGNORE_EINTR(close(available_fds[i])));
    433   }
    434 
    435   // Lower our file descriptor limit to just allow three more file descriptors
    436   // to be allocated.  (N.B., RLIMIT_NOFILE doesn't limit the number of file
    437   // descriptors a process can have: it only limits the highest value that can
    438   // be assigned to newly-created descriptors allocated by the process.)
    439   const rlim_t fd_limit =
    440       1 + *std::max_element(available_fds,
    441                             available_fds + arraysize(available_fds));
    442 
    443   // Valgrind doesn't allow changing the hard descriptor limit, so we only
    444   // change the soft descriptor limit here.
    445   struct rlimit rlim;
    446   SANDBOX_ASSERT(0 == getrlimit(RLIMIT_NOFILE, &rlim));
    447   SANDBOX_ASSERT(fd_limit <= rlim.rlim_cur);
    448   rlim.rlim_cur = fd_limit;
    449   SANDBOX_ASSERT(0 == setrlimit(RLIMIT_NOFILE, &rlim));
    450 
    451   static const char kCpuInfo[] = "/proc/cpuinfo";
    452   std::vector<std::string> read_whitelist;
    453   read_whitelist.push_back(kCpuInfo);
    454 
    455   BrokerProcess open_broker(EPERM, read_whitelist, std::vector<std::string>());
    456   SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback)));
    457 
    458   const int ipc_fd = BrokerProcessTestHelper::get_ipc_socketpair(&open_broker);
    459   SANDBOX_ASSERT(ipc_fd >= 0);
    460 
    461   static const char kBogus[] = "not a pickle";
    462   std::vector<int> fds;
    463   fds.push_back(message_fd.get());
    464 
    465   // The broker process should only have a couple spare file descriptors
    466   // available, but for good measure we send it fd_limit bogus IPCs anyway.
    467   for (rlim_t i = 0; i < fd_limit; ++i) {
    468     SANDBOX_ASSERT(
    469         UnixDomainSocket::SendMsg(ipc_fd, kBogus, sizeof(kBogus), fds));
    470   }
    471 
    472   const int fd = open_broker.Open(kCpuInfo, O_RDONLY);
    473   SANDBOX_ASSERT(fd >= 0);
    474   SANDBOX_ASSERT(0 == IGNORE_EINTR(close(fd)));
    475 }
    476 
    477 }  // namespace sandbox
    478