summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
blob: 2a9dcfff4004be076be69b1927e257a6a9fe5b28 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SandboxBrokerCommon.h"

#include "mozilla/Assertions.h"

// This file is built both within libxul and as a separate libmozsandbox
// library. We can only use profiler annotations within libxul.
#ifdef MOZILLA_INTERNAL_API
#  include "mozilla/ProfilerThreadSleep.h"
#else
#  define AUTO_PROFILER_THREAD_SLEEP
#endif

#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#ifndef MSG_CMSG_CLOEXEC
#  ifdef XP_LINUX
// As always, Android's kernel headers are somewhat old.
#    define MSG_CMSG_CLOEXEC 0x40000000
#  else
// Most of this code can support other POSIX OSes, but being able to
// receive fds and atomically make them close-on-exec is important,
// because this is running in a multithreaded process that can fork.
// In the future, if the broker becomes a dedicated executable, this
// can change.
#    error "No MSG_CMSG_CLOEXEC?"
#  endif  // XP_LINUX
#endif    // MSG_CMSG_CLOEXEC

namespace mozilla {

const char* SandboxBrokerCommon::OperationDescription[] = {
    "open",
    "access",
    "stat",
    "chmod",
    "link",
    "symlink",
    "mkdir",
    "rename",
    "rmdir",
    "unlink",
    "readlink",
    "connect",
    "connect-abstract",
};

/* static */
ssize_t SandboxBrokerCommon::RecvWithFd(int aFd, const iovec* aIO,
                                        size_t aNumIO, int* aPassedFdPtr) {
  struct msghdr msg = {};
  msg.msg_iov = const_cast<iovec*>(aIO);
  msg.msg_iovlen = aNumIO;

  char cmsg_buf[CMSG_SPACE(sizeof(int))];
  if (aPassedFdPtr) {
    msg.msg_control = cmsg_buf;
    msg.msg_controllen = sizeof(cmsg_buf);
    *aPassedFdPtr = -1;
  }

  ssize_t rv;
  do {
    // MSG_CMSG_CLOEXEC is needed to prevent the parent process from
    // accidentally leaking a copy of the child's response socket to a
    // new child process.  (The child won't be able to exec, so this
    // doesn't matter as much for that direction.)
    AUTO_PROFILER_THREAD_SLEEP;
    rv = recvmsg(aFd, &msg, MSG_CMSG_CLOEXEC);
  } while (rv < 0 && errno == EINTR);

  if (rv <= 0) {
    return rv;
  }
  if (msg.msg_controllen > 0) {
    MOZ_ASSERT(aPassedFdPtr);
    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
      int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
      if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
        // A client could, for example, send an extra 32-bit int if
        // CMSG_SPACE pads to 64-bit size_t alignment.  If so, treat
        // it as an error, but also don't leak the fds.
        for (size_t i = 0; CMSG_LEN(sizeof(int) * i) < cmsg->cmsg_len; ++i) {
          close(fds[i]);
        }
        // In theory, the kernel should delete the message instead of
        // giving us an empty one, if errors prevent transferring the
        // fd.
        MOZ_DIAGNOSTIC_ASSERT(cmsg->cmsg_len != 0);
        errno = EMSGSIZE;
        return -1;
      }
      *aPassedFdPtr = fds[0];
    } else {
      errno = EPROTO;
      return -1;
    }
  }
  if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
    if (aPassedFdPtr && *aPassedFdPtr >= 0) {
      close(*aPassedFdPtr);
      *aPassedFdPtr = -1;
    }
    // MSG_CTRUNC usually means the fd was dropped due to fd
    // exhaustion in the receiving process, so map that to `EMFILE`.
    // MSG_TRUNC (truncation of the data part) shouldn't ever happen.
    // (If the sender is malicious it can send too many bytes or fds,
    // but this is about getting an accurate error message in genuine
    // error cases.)
    MOZ_DIAGNOSTIC_ASSERT((msg.msg_flags & MSG_TRUNC) == 0);
    errno = EMFILE;
    return -1;
  }

  return rv;
}

/* static */
ssize_t SandboxBrokerCommon::SendWithFd(int aFd, const iovec* aIO,
                                        size_t aNumIO, int aPassedFd) {
  struct msghdr msg = {};
  msg.msg_iov = const_cast<iovec*>(aIO);
  msg.msg_iovlen = aNumIO;

  char cmsg_buf[CMSG_SPACE(sizeof(int))];
  memset(cmsg_buf, 0, sizeof(cmsg_buf));
  if (aPassedFd != -1) {
    msg.msg_control = cmsg_buf;
    msg.msg_controllen = sizeof(cmsg_buf);
    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    *reinterpret_cast<int*>(CMSG_DATA(cmsg)) = aPassedFd;
  }

  ssize_t rv;
  do {
    rv = sendmsg(aFd, &msg, MSG_NOSIGNAL);
  } while (rv < 0 && errno == EINTR);

  return rv;
}

}  // namespace mozilla