summaryrefslogtreecommitdiffstats
path: root/sandbox/linux/seccomp/socketcall.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/linux/seccomp/socketcall.cc')
-rw-r--r--sandbox/linux/seccomp/socketcall.cc1039
1 files changed, 1039 insertions, 0 deletions
diff --git a/sandbox/linux/seccomp/socketcall.cc b/sandbox/linux/seccomp/socketcall.cc
new file mode 100644
index 0000000..c7b2015
--- /dev/null
+++ b/sandbox/linux/seccomp/socketcall.cc
@@ -0,0 +1,1039 @@
+// 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 "debug.h"
+#include "sandbox_impl.h"
+
+namespace playground {
+
+#if defined(__NR_socket)
+
+ssize_t Sandbox::sandbox_recvfrom(int sockfd, void* buf, size_t len, int flags,
+ void* from, socklen_t* fromlen) {
+ long long tm;
+ Debug::syscall(&tm, __NR_recvfrom, "Executing handler");
+
+ SysCalls sys;
+ if (!from && !flags) {
+ // recv() with a NULL sender and no flags is the same as read(), which
+ // is unrestricted in seccomp mode.
+ Debug::message("Replaced recv() with call to read()");
+ ssize_t rc = sys.read(sockfd, buf, len);
+ if (rc < 0) {
+ Debug::elapsed(tm, __NR_recvfrom);
+ return -sys.my_errno;
+ } else {
+ Debug::elapsed(tm, __NR_recvfrom);
+ return rc;
+ }
+ }
+
+ struct {
+ int sysnum;
+ long long cookie;
+ RecvFrom recvfrom_req;
+ } __attribute__((packed)) request;
+ request.sysnum = __NR_recvfrom;
+ request.cookie = cookie();
+ request.recvfrom_req.sockfd = sockfd;
+ request.recvfrom_req.buf = buf;
+ request.recvfrom_req.len = len;
+ request.recvfrom_req.flags = flags;
+ request.recvfrom_req.from = from;
+ request.recvfrom_req.fromlen = fromlen;
+
+ long rc;
+ if (write(sys, processFdPub(), &request, sizeof(request)) !=
+ sizeof(request) ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward recvfrom() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_recvfrom);
+ return static_cast<ssize_t>(rc);
+}
+
+ssize_t Sandbox::sandbox_recvmsg(int sockfd, struct msghdr* msg, int flags) {
+ long long tm;
+ Debug::syscall(&tm, __NR_recvmsg, "Executing handler");
+
+ // We cannot simplify recvmsg() to recvfrom(), recv() or read(), as we do
+ // not know whether the caller needs us to set msg->msg_flags.
+ struct {
+ int sysnum;
+ long long cookie;
+ RecvMsg recvmsg_req;
+ } __attribute__((packed)) request;
+ request.sysnum = __NR_recvmsg;
+ request.cookie = cookie();
+ request.recvmsg_req.sockfd = sockfd;
+ request.recvmsg_req.msg = msg;
+ request.recvmsg_req.flags = flags;
+
+ long rc;
+ SysCalls sys;
+ if (write(sys, processFdPub(), &request, sizeof(request)) !=
+ sizeof(request) ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward recvmsg() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_recvmsg);
+ return static_cast<ssize_t>(rc);
+}
+
+size_t Sandbox::sandbox_sendmsg(int sockfd, const struct msghdr* msg,
+ int flags) {
+ long long tm;
+ Debug::syscall(&tm, __NR_sendmsg, "Executing handler");
+
+ if (msg->msg_iovlen == 1 && msg->msg_controllen == 0) {
+ // sendmsg() can sometimes be simplified as sendto()
+ return sandbox_sendto(sockfd, msg->msg_iov, msg->msg_iovlen,
+ flags, msg->msg_name, msg->msg_namelen);
+ }
+
+ struct Request {
+ int sysnum;
+ long long cookie;
+ SendMsg sendmsg_req;
+ struct msghdr msg;
+ } __attribute__((packed));
+ char data[sizeof(struct Request) + msg->msg_namelen + msg->msg_controllen];
+ struct Request *request = reinterpret_cast<struct Request *>(data);
+ request->sysnum = __NR_sendmsg;
+ request->cookie = cookie();
+ request->sendmsg_req.sockfd = sockfd;
+ request->sendmsg_req.msg = msg;
+ request->sendmsg_req.flags = flags;
+ request->msg = *msg;
+ memcpy(reinterpret_cast<char *>(
+ memcpy(request + 1, msg->msg_name, msg->msg_namelen)) +
+ msg->msg_namelen,
+ msg->msg_control, msg->msg_controllen);
+
+ long rc;
+ SysCalls sys;
+ if (write(sys, processFdPub(), &data, sizeof(data)) !=
+ (ssize_t)sizeof(data) ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward sendmsg() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_sendmsg);
+ return static_cast<ssize_t>(rc);
+}
+
+ssize_t Sandbox::sandbox_sendto(int sockfd, const void* buf, size_t len,
+ int flags, const void* to, socklen_t tolen) {
+ long long tm;
+ Debug::syscall(&tm, __NR_sendto, "Executing handler");
+
+ SysCalls sys;
+ if (!to && !flags) {
+ // sendto() with a NULL recipient and no flags is the same as write(),
+ // which is unrestricted in seccomp mode.
+ Debug::message("Replaced sendto() with call to write()");
+ ssize_t rc = sys.write(sockfd, buf, len);
+ if (rc < 0) {
+ Debug::elapsed(tm, __NR_sendto);
+ return -sys.my_errno;
+ } else {
+ Debug::elapsed(tm, __NR_sendto);
+ return rc;
+ }
+ }
+
+ struct {
+ int sysnum;
+ long long cookie;
+ SendTo sendto_req;
+ } __attribute__((packed)) request;
+ request.sysnum = __NR_sendto;
+ request.cookie = cookie();
+ request.sendto_req.sockfd = sockfd;
+ request.sendto_req.buf = buf;
+ request.sendto_req.len = len;
+ request.sendto_req.flags = flags;
+ request.sendto_req.to = to;
+ request.sendto_req.tolen = tolen;
+
+ long rc;
+ if (write(sys, processFdPub(), &request, sizeof(request)) !=
+ sizeof(request) ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward sendto() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_sendto);
+ return static_cast<ssize_t>(rc);
+}
+
+long Sandbox::sandbox_setsockopt(int sockfd, int level, int optname,
+ const void* optval, socklen_t optlen) {
+ long long tm;
+ Debug::syscall(&tm, __NR_setsockopt, "Executing handler");
+
+ struct {
+ int sysnum;
+ long long cookie;
+ SetSockOpt setsockopt_req;
+ } __attribute__((packed)) request;
+ request.sysnum = __NR_setsockopt;
+ request.cookie = cookie();
+ request.setsockopt_req.sockfd = sockfd;
+ request.setsockopt_req.level = level;
+ request.setsockopt_req.optname = optname;
+ request.setsockopt_req.optval = optval;
+ request.setsockopt_req.optlen = optlen;
+
+ long rc;
+ SysCalls sys;
+ if (write(sys, processFdPub(), &request, sizeof(request)) !=
+ sizeof(request) ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward setsockopt() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_setsockopt);
+ return rc;
+}
+
+long Sandbox::sandbox_getsockopt(int sockfd, int level, int optname,
+ void* optval, socklen_t* optlen) {
+ long long tm;
+ Debug::syscall(&tm, __NR_getsockopt, "Executing handler");
+
+ struct {
+ int sysnum;
+ long long cookie;
+ GetSockOpt getsockopt_req;
+ } __attribute__((packed)) request;
+ request.sysnum = __NR_getsockopt;
+ request.cookie = cookie();
+ request.getsockopt_req.sockfd = sockfd;
+ request.getsockopt_req.level = level;
+ request.getsockopt_req.optname = optname;
+ request.getsockopt_req.optval = optval;
+ request.getsockopt_req.optlen = optlen;
+
+ long rc;
+ SysCalls sys;
+ if (write(sys, processFdPub(), &request, sizeof(request)) !=
+ sizeof(request) ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward getsockopt() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_getsockopt);
+ return rc;
+}
+
+bool Sandbox::process_recvfrom(int parentMapsFd, int sandboxFd,
+ int threadFdPub, int threadFd,
+ SecureMem::Args* mem) {
+ // Read request
+ RecvFrom recvfrom_req;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &recvfrom_req, sizeof(recvfrom_req)) !=
+ sizeof(recvfrom_req)) {
+ die("Failed to read parameters for recvfrom() [process]");
+ }
+
+ // Unsupported flag encountered. Deny the call.
+ if (recvfrom_req.flags &
+ ~(MSG_DONTWAIT|MSG_OOB|MSG_PEEK|MSG_TRUNC|MSG_WAITALL)) {
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+ }
+
+ // While we do not anticipate any particular need to receive data on
+ // unconnected sockets, there is no particular risk in doing so.
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_recvfrom, recvfrom_req.sockfd,
+ recvfrom_req.buf, recvfrom_req.len,
+ recvfrom_req.flags, recvfrom_req.from,
+ recvfrom_req.fromlen);
+ return true;
+}
+
+bool Sandbox::process_recvmsg(int parentMapsFd, int sandboxFd, int threadFdPub,
+ int threadFd, SecureMem::Args* mem) {
+ // Read request
+ RecvMsg recvmsg_req;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &recvmsg_req, sizeof(recvmsg_req)) !=
+ sizeof(recvmsg_req)) {
+ die("Failed to read parameters for recvmsg() [process]");
+ }
+
+ // Unsupported flag encountered. Deny the call.
+ if (recvmsg_req.flags &
+ ~(MSG_DONTWAIT|MSG_OOB|MSG_PEEK|MSG_TRUNC|MSG_WAITALL)) {
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+ }
+
+ // Receiving messages is general not security critical.
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_recvmsg, recvmsg_req.sockfd,
+ recvmsg_req.msg, recvmsg_req.flags);
+ return true;
+}
+
+bool Sandbox::process_sendmsg(int parentMapsFd, int sandboxFd, int threadFdPub,
+ int threadFd, SecureMem::Args* mem) {
+ // Read request
+ struct {
+ SendMsg sendmsg_req;
+ struct msghdr msg;
+ } __attribute__((packed)) data;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &data, sizeof(data)) != sizeof(data)) {
+ die("Failed to read parameters for sendmsg() [process]");
+ }
+
+ if (data.msg.msg_namelen > 4096 || data.msg.msg_controllen > 4096) {
+ die("Unexpected size for socketcall() payload [process]");
+ }
+ char extra[data.msg.msg_namelen + data.msg.msg_controllen];
+ if (read(sys, sandboxFd, &extra, sizeof(extra)) != (ssize_t)sizeof(extra)) {
+ die("Failed to read parameters for sendmsg() [process]");
+ }
+ if (sizeof(struct msghdr) + sizeof(extra) > sizeof(mem->pathname)) {
+ goto deny;
+ }
+
+ if (data.msg.msg_namelen ||
+ (data.sendmsg_req.flags &
+ ~(MSG_CONFIRM|MSG_DONTWAIT|MSG_EOR|MSG_MORE|MSG_NOSIGNAL|MSG_OOB))) {
+ deny:
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+ }
+
+ // The trusted process receives file handles when a new untrusted thread
+ // gets created. We have security checks in place that prevent any
+ // critical information from being tampered with during thread creation.
+ // But if we disallowed passing of file handles, this would add an extra
+ // hurdle for an attacker.
+ // Unfortunately, for now, this is not possible as Chrome's
+ // base::SendRecvMsg() needs the ability to pass file handles.
+ if (data.msg.msg_controllen) {
+ data.msg.msg_control = extra + data.msg.msg_namelen;
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&data.msg);
+ do {
+ if (cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS) {
+ goto deny;
+ }
+ } while ((cmsg = CMSG_NXTHDR(&data.msg, cmsg)) != NULL);
+ }
+
+ // This must be a locked system call, because we have to ensure that the
+ // untrusted code does not tamper with the msghdr after we have examined it.
+ SecureMem::lockSystemCall(parentMapsFd, mem);
+ if (sizeof(extra) > 0) {
+ if (data.msg.msg_namelen > 0) {
+ data.msg.msg_name = mem->pathname + sizeof(struct msghdr);
+ }
+ if (data.msg.msg_controllen > 0) {
+ data.msg.msg_control = mem->pathname + sizeof(struct msghdr) +
+ data.msg.msg_namelen;
+ }
+ memcpy(mem->pathname + sizeof(struct msghdr), extra, sizeof(extra));
+ }
+ memcpy(mem->pathname, &data.msg, sizeof(struct msghdr));
+ SecureMem::sendSystemCall(threadFdPub, true, parentMapsFd, mem,
+ __NR_sendmsg, data.sendmsg_req.sockfd,
+ mem->pathname - (char*)mem + (char*)mem->self,
+ data.sendmsg_req.flags);
+ return true;
+}
+
+bool Sandbox::process_sendto(int parentMapsFd, int sandboxFd, int threadFdPub,
+ int threadFd, SecureMem::Args* mem) {
+ // Read request
+ SendTo sendto_req;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &sendto_req, sizeof(sendto_req)) !=
+ sizeof(sendto_req)) {
+ die("Failed to read parameters for sendto() [process]");
+ }
+
+ // The sandbox does not allow sending to arbitrary addresses.
+ if (sendto_req.to) {
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+ }
+
+ // Unsupported flag encountered. Deny the call.
+ if (sendto_req.flags &
+ ~(MSG_CONFIRM|MSG_DONTWAIT|MSG_EOR|MSG_MORE|MSG_NOSIGNAL|MSG_OOB)) {
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+ }
+
+ // Sending data on a connected socket is similar to calling write().
+ // Allow it.
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_sendto, sendto_req.sockfd,
+ sendto_req.buf, sendto_req.len,
+ sendto_req.flags, sendto_req.to,
+ sendto_req.tolen);
+ return true;
+}
+
+bool Sandbox::process_setsockopt(int parentMapsFd, int sandboxFd,
+ int threadFdPub, int threadFd,
+ SecureMem::Args* mem) {
+ // Read request
+ SetSockOpt setsockopt_req;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &setsockopt_req, sizeof(setsockopt_req)) !=
+ sizeof(setsockopt_req)) {
+ die("Failed to read parameters for setsockopt() [process]");
+ }
+
+ switch (setsockopt_req.level) {
+ case SOL_SOCKET:
+ switch (setsockopt_req.optname) {
+ case SO_KEEPALIVE:
+ case SO_LINGER:
+ case SO_OOBINLINE:
+ case SO_RCVBUF:
+ case SO_RCVLOWAT:
+ case SO_SNDLOWAT:
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ case SO_REUSEADDR:
+ case SO_SNDBUF:
+ case SO_TIMESTAMP:
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_setsockopt, setsockopt_req.sockfd,
+ setsockopt_req.level, setsockopt_req.optname,
+ setsockopt_req.optval, setsockopt_req.optlen);
+ return true;
+ default:
+ break;
+ }
+ break;
+ case IPPROTO_TCP:
+ switch (setsockopt_req.optname) {
+ case TCP_CORK:
+ case TCP_DEFER_ACCEPT:
+ case TCP_INFO:
+ case TCP_KEEPCNT:
+ case TCP_KEEPIDLE:
+ case TCP_KEEPINTVL:
+ case TCP_LINGER2:
+ case TCP_MAXSEG:
+ case TCP_NODELAY:
+ case TCP_QUICKACK:
+ case TCP_SYNCNT:
+ case TCP_WINDOW_CLAMP:
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_setsockopt, setsockopt_req.sockfd,
+ setsockopt_req.level, setsockopt_req.optname,
+ setsockopt_req.optval, setsockopt_req.optlen);
+ return true;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+}
+
+bool Sandbox::process_getsockopt(int parentMapsFd, int sandboxFd,
+ int threadFdPub, int threadFd,
+ SecureMem::Args* mem) {
+ // Read request
+ GetSockOpt getsockopt_req;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &getsockopt_req, sizeof(getsockopt_req)) !=
+ sizeof(getsockopt_req)) {
+ die("Failed to read parameters for getsockopt() [process]");
+ }
+
+ switch (getsockopt_req.level) {
+ case SOL_SOCKET:
+ switch (getsockopt_req.optname) {
+ case SO_ACCEPTCONN:
+ case SO_ERROR:
+ case SO_KEEPALIVE:
+ case SO_LINGER:
+ case SO_OOBINLINE:
+ case SO_RCVBUF:
+ case SO_RCVLOWAT:
+ case SO_SNDLOWAT:
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ case SO_REUSEADDR:
+ case SO_SNDBUF:
+ case SO_TIMESTAMP:
+ case SO_TYPE:
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_getsockopt, getsockopt_req.sockfd,
+ getsockopt_req.level, getsockopt_req.optname,
+ getsockopt_req.optval, getsockopt_req.optlen);
+ return true;
+ default:
+ break;
+ }
+ break;
+ case IPPROTO_TCP:
+ switch (getsockopt_req.optname) {
+ case TCP_CORK:
+ case TCP_DEFER_ACCEPT:
+ case TCP_INFO:
+ case TCP_KEEPCNT:
+ case TCP_KEEPIDLE:
+ case TCP_KEEPINTVL:
+ case TCP_LINGER2:
+ case TCP_MAXSEG:
+ case TCP_NODELAY:
+ case TCP_QUICKACK:
+ case TCP_SYNCNT:
+ case TCP_WINDOW_CLAMP:
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem,
+ __NR_getsockopt, getsockopt_req.sockfd,
+ getsockopt_req.level, getsockopt_req.optname,
+ getsockopt_req.optval, getsockopt_req.optlen);
+ return true;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ SecureMem::abandonSystemCall(threadFd, -EINVAL);
+ return false;
+}
+
+#endif
+#if defined(__NR_socketcall)
+
+enum {
+ SYS_SOCKET = 1,
+ SYS_BIND = 2,
+ SYS_CONNECT = 3,
+ SYS_LISTEN = 4,
+ SYS_ACCEPT = 5,
+ SYS_GETSOCKNAME = 6,
+ SYS_GETPEERNAME = 7,
+ SYS_SOCKETPAIR = 8,
+ SYS_SEND = 9,
+ SYS_RECV = 10,
+ SYS_SENDTO = 11,
+ SYS_RECVFROM = 12,
+ SYS_SHUTDOWN = 13,
+ SYS_SETSOCKOPT = 14,
+ SYS_GETSOCKOPT = 15,
+ SYS_SENDMSG = 16,
+ SYS_RECVMSG = 17,
+ SYS_ACCEPT4 = 18
+};
+
+struct Sandbox::SocketCallArgInfo {
+ size_t len;
+ off_t addrOff;
+ off_t lengthOff;
+};
+const struct Sandbox::SocketCallArgInfo Sandbox::socketCallArgInfo[] = {
+ #define STRUCT(s) reinterpret_cast<SocketCall *>(0)->args.s
+ #define SIZE(s) sizeof(STRUCT(s))
+ #define OFF(s, f) offsetof(typeof STRUCT(s), f)
+ { 0 },
+ { SIZE(socket) },
+ { SIZE(bind), OFF(bind, addr), OFF(bind, addrlen) },
+ { SIZE(connect), OFF(connect, addr), OFF(connect, addrlen) },
+ { SIZE(listen) },
+ { SIZE(accept) },
+ { SIZE(getsockname) },
+ { SIZE(getpeername) },
+ { SIZE(socketpair) },
+ { SIZE(send) },
+ { SIZE(recv) },
+ { SIZE(sendto), OFF(sendto, to), OFF(sendto, tolen) },
+ { SIZE(recvfrom) },
+ { SIZE(shutdown) },
+ { SIZE(setsockopt), OFF(setsockopt, optval), OFF(setsockopt, optlen) },
+ { SIZE(getsockopt) },
+ { SIZE(sendmsg) },
+ { SIZE(recvmsg) },
+ { SIZE(accept4) }
+ #undef STRUCT
+ #undef SIZE
+ #undef OFF
+};
+
+long Sandbox::sandbox_socketcall(int call, void* args) {
+ long long tm;
+ Debug::syscall(&tm, __NR_socketcall, "Executing handler", call);
+
+ // When demultiplexing socketcall(), only accept calls that have a valid
+ // "call" opcode.
+ if (call < SYS_SOCKET || call > SYS_ACCEPT4) {
+ Debug::elapsed(tm, __NR_socketcall, call);
+ return -ENOSYS;
+ }
+
+ // Some type of calls include a pointer to an address or name, which cannot
+ // be accessed by the trusted process, as it lives in a separate address
+ // space. For these calls, append the extra data to the serialized request.
+ // This requires some copying of data, as we have to make sure there is
+ // only a single atomic call to write().
+ socklen_t numExtraData = 0;
+ const void* extraDataAddr = NULL;
+ if (socketCallArgInfo[call].lengthOff) {
+ memcpy(&numExtraData,
+ reinterpret_cast<char *>(args) + socketCallArgInfo[call].lengthOff,
+ sizeof(socklen_t));
+ extraDataAddr = reinterpret_cast<char *>(args) +
+ socketCallArgInfo[call].addrOff;
+ }
+
+ // sendmsg() and recvmsg() have more complicated requirements for computing
+ // the amount of extra data that needs to be sent to the trusted process.
+ if (call == SYS_SENDMSG) {
+ SendMsg *sendmsg_args = reinterpret_cast<SendMsg *>(args);
+ if (sendmsg_args->msg->msg_iovlen == 1 &&
+ !sendmsg_args->msg->msg_control) {
+ // Further down in the code, this sendmsg() call will be simplified to
+ // a sendto() call. Make sure we already compute the correct value for
+ // numExtraData, as it is needed when we allocate "data[]" on the stack.
+ numExtraData = sendmsg_args->msg->msg_namelen;
+ extraDataAddr = sendmsg_args->msg->msg_name;
+ } else {
+ // sendmsg() needs to include some of the extra data so that we can
+ // inspect it in process_socketcall()
+ numExtraData = sizeof(*sendmsg_args->msg) +
+ sendmsg_args->msg->msg_namelen +
+ sendmsg_args->msg->msg_controllen;
+ extraDataAddr = NULL;
+ }
+ }
+ if (call == SYS_RECVMSG) {
+ RecvMsg *recvmsg_args = reinterpret_cast<RecvMsg *>(args);
+ numExtraData = sizeof(*recvmsg_args->msg);
+ extraDataAddr = recvmsg_args->msg;
+ }
+
+ // Set up storage for the request header and copy the data from "args"
+ // into it.
+ struct Request {
+ int sysnum;
+ long long cookie;
+ SocketCall socketcall_req;
+ } __attribute__((packed)) *request;
+ char data[sizeof(struct Request) + numExtraData];
+ request = reinterpret_cast<struct Request *>(data);
+ memcpy(&request->socketcall_req.args, args, socketCallArgInfo[call].len);
+
+ // Simplify send(), sendto() and sendmsg(), if there are simpler equivalent
+ // calls. This allows us to occasionally replace them with calls to write(),
+ // which don't have to be forwarded to the trusted process.
+ SysCalls sys;
+ if (call == SYS_SENDMSG &&
+ request->socketcall_req.args.sendmsg.msg->msg_iovlen == 1 &&
+ !request->socketcall_req.args.sendmsg.msg->msg_control) {
+ // Ordering of these assignments is important, as we are reshuffling
+ // fields inside of a union.
+ call = SYS_SENDTO;
+ request->socketcall_req.args.sendto.flags =
+ request->socketcall_req.args.sendmsg.flags;
+ request->socketcall_req.args.sendto.to =
+ request->socketcall_req.args.sendmsg.msg->msg_name;
+ request->socketcall_req.args.sendto.tolen =
+ request->socketcall_req.args.sendmsg.msg->msg_namelen;
+ request->socketcall_req.args.sendto.len =
+ request->socketcall_req.args.sendmsg.msg->msg_iov->iov_len;
+ request->socketcall_req.args.sendto.buf =
+ request->socketcall_req.args.sendmsg.msg->msg_iov->iov_base;
+ }
+ if (call == SYS_SENDTO && !request->socketcall_req.args.sendto.to) {
+ // sendto() with a NULL address is the same as send()
+ call = SYS_SEND;
+ numExtraData = 0;
+ }
+ if (call == SYS_SEND && !request->socketcall_req.args.send.flags) {
+ // send() with no flags is the same as write(), which is unrestricted
+ // in seccomp mode.
+ Debug::message("Replaced socketcall() with call to write()");
+ ssize_t rc = sys.write(request->socketcall_req.args.send.sockfd,
+ request->socketcall_req.args.send.buf,
+ request->socketcall_req.args.send.len);
+ if (rc < 0) {
+ Debug::elapsed(tm, __NR_socketcall, call);
+ return -sys.my_errno;
+ } else {
+ Debug::elapsed(tm, __NR_socketcall, call);
+ return rc;
+ }
+ }
+
+ // Simplify recv(), and recvfrom(), if there are simpler equivalent calls.
+ // This allows us to occasionally replace them with calls to read(), which
+ // don't have to be forwarded to the trusted process.
+ // We cannot simplify recvmsg() to recvfrom(), recv() or read(), as we do
+ // not know whether the caller needs us to set msg->msg_flags.
+ if (call == SYS_RECVFROM && !request->socketcall_req.args.recvfrom.from) {
+ // recvfrom() with a NULL address buffer is the same as recv()
+ call = SYS_RECV;
+ }
+ if (call == SYS_RECV && !request->socketcall_req.args.recv.flags) {
+ // recv() with no flags is the same as read(), which is unrestricted
+ // in seccomp mode.
+ Debug::message("Replaced socketcall() with call to read()");
+ ssize_t rc = sys.read(request->socketcall_req.args.recv.sockfd,
+ request->socketcall_req.args.recv.buf,
+ request->socketcall_req.args.recv.len);
+ if (rc < 0) {
+ Debug::elapsed(tm, __NR_socketcall, call);
+ return -sys.my_errno;
+ } else {
+ Debug::elapsed(tm, __NR_socketcall, call);
+ return rc;
+ }
+ }
+
+ // Fill in the rest of the request header.
+ request->sysnum = __NR_socketcall;
+ request->cookie = cookie();
+ request->socketcall_req.call = call;
+ request->socketcall_req.arg_ptr = args;
+ int padding = sizeof(request->socketcall_req.args) -
+ socketCallArgInfo[call].len;
+ if (padding > 0) {
+ memset((char *)(&request->socketcall_req.args + 1) - padding, 0, padding);
+ }
+ if (call == SYS_SENDMSG) {
+ // for sendmsg() we include the (optional) destination address, and the
+ // (optional) control data in the payload.
+ SendMsg *sendmsg_args = reinterpret_cast<SendMsg *>(args);
+ memcpy(reinterpret_cast<char *>(
+ memcpy(reinterpret_cast<char *>(
+ memcpy(request + 1, sendmsg_args->msg, sizeof(*sendmsg_args->msg))) +
+ sizeof(*sendmsg_args->msg),
+ sendmsg_args->msg->msg_name, sendmsg_args->msg->msg_namelen)) +
+ sendmsg_args->msg->msg_namelen,
+ sendmsg_args->msg->msg_control, sendmsg_args->msg->msg_controllen);
+ } else if (extraDataAddr) {
+ memcpy(request + 1, extraDataAddr, numExtraData);
+ }
+
+ // Send request to trusted process and collect response from trusted thread.
+ long rc;
+ ssize_t len = sizeof(struct Request) + numExtraData;
+ if (write(sys, processFdPub(), data, len) != len ||
+ read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) {
+ die("Failed to forward socketcall() request [sandbox]");
+ }
+ Debug::elapsed(tm, __NR_socketcall, call);
+ return rc;
+}
+
+bool Sandbox::process_socketcall(int parentMapsFd, int sandboxFd,
+ int threadFdPub, int threadFd,
+ SecureMem::Args* mem) {
+ // Read request
+ SocketCall socketcall_req;
+ SysCalls sys;
+ if (read(sys, sandboxFd, &socketcall_req, sizeof(socketcall_req)) !=
+ sizeof(socketcall_req)) {
+ die("Failed to read parameters for socketcall() [process]");
+ }
+
+ // sandbox_socketcall() should never send us an unexpected "call" opcode.
+ // If it did, something went very wrong and we better terminate the process.
+ if (socketcall_req.call < SYS_SOCKET || socketcall_req.call > SYS_ACCEPT4) {
+ die("Unexpected socketcall() [process]");
+ }
+
+ // Check if this particular operation carries an extra payload.
+ socklen_t numExtraData = 0;
+ if (socketCallArgInfo[socketcall_req.call].lengthOff) {
+ memcpy(&numExtraData,
+ reinterpret_cast<char *>(&socketcall_req) +
+ socketCallArgInfo[socketcall_req.call].lengthOff,
+ sizeof(socklen_t));
+ } else if (socketcall_req.call == SYS_SENDMSG) {
+ numExtraData = sizeof(*socketcall_req.args.sendmsg.msg);
+ } else if (socketcall_req.call == SYS_RECVMSG) {
+ numExtraData = sizeof(*socketcall_req.args.recvmsg.msg);
+ }
+
+ // Verify that the length for the payload is reasonable. We don't want to
+ // blow up our stack, and excessive (or negative) buffer sizes are almost
+ // certainly a bug.
+ if (numExtraData > 4096) {
+ die("Unexpected size for socketcall() payload [process]");
+ }
+
+ // Read the extra payload, if any.
+ char extra[numExtraData];
+ if (numExtraData) {
+ if (read(sys, sandboxFd, extra, numExtraData) != (ssize_t)numExtraData) {
+ die("Failed to read socketcall() payload [process]");
+ }
+ }
+
+ // sendmsg() has another level of indirection and can carry even more payload
+ ssize_t numSendmsgExtra = 0;
+ if (socketcall_req.call == SYS_SENDMSG) {
+ struct msghdr* msg = reinterpret_cast<struct msghdr*>(extra);
+ if (msg->msg_namelen > 4096 || msg->msg_controllen > 4096) {
+ die("Unexpected size for socketcall() payload [process]");
+ }
+ numSendmsgExtra = msg->msg_namelen + msg->msg_controllen;
+ }
+ char sendmsgExtra[numSendmsgExtra];
+ if (numSendmsgExtra) {
+ if (read(sys, sandboxFd, sendmsgExtra, numSendmsgExtra) !=
+ numSendmsgExtra) {
+ die("Failed to read socketcall() payload [process]");
+ }
+ }
+
+ int rc = -EINVAL;
+ switch (socketcall_req.call) {
+ case SYS_SOCKET:
+ // The sandbox does not allow creation of any new sockets.
+ goto deny;
+ case SYS_BIND:
+ // The sandbox does not allow binding an address to a socket.
+ goto deny;
+ case SYS_CONNECT:
+ // The sandbox does not allow connecting a socket.
+ goto deny;
+ case SYS_LISTEN:
+ // The sandbox does not allow a socket to enter listening state.
+ goto deny;
+ case SYS_ACCEPT4:
+ case SYS_ACCEPT:
+ // If the sandbox obtained a socket that is already in the listening
+ // state (e.g. because somebody sent it a suitable file descriptor), it
+ // is permissible to call accept().
+
+ accept_simple:
+ // None of the parameters need to be checked, so it is OK to refer
+ // to the parameter block created by the untrusted code.
+ SecureMem::sendSystemCall(threadFdPub, false, -1, mem, __NR_socketcall,
+ socketcall_req.call, socketcall_req.arg_ptr);
+ return true;
+ case SYS_GETSOCKNAME:
+ case SYS_GETPEERNAME:
+ // Querying the local and the remote name is not considered security
+ // sensitive for the purposes of the sandbox.
+ goto accept_simple;
+ case SYS_SOCKETPAIR:
+ // Socket pairs are connected to each other and not considered
+ // security sensitive.
+ goto accept_simple;
+ case SYS_SENDTO:
+ if (socketcall_req.args.sendto.to) {
+ // The sandbox does not allow sending to arbitrary addresses.
+ goto deny;
+ }
+ // Fall through
+ case SYS_SEND:
+ if (socketcall_req.args.send.flags &
+ ~(MSG_CONFIRM|MSG_DONTWAIT|MSG_EOR|MSG_MORE|MSG_NOSIGNAL|MSG_OOB)) {
+ // Unsupported flag encountered. Deny the call.
+ goto deny;
+ }
+ // Sending data on a connected socket is similar to calling write().
+ // Allow it.
+
+ accept_complex:
+ // The parameter block contains potentially security critical information
+ // that should not be tampered with after it has been inspected. Copy it
+ // into the write-protected securely shared memory before telling the
+ // trusted thread to execute the socket call.
+ SecureMem::lockSystemCall(parentMapsFd, mem);
+ memcpy(mem->pathname, &socketcall_req.args, sizeof(socketcall_req.args));
+ SecureMem::sendSystemCall(threadFdPub, true, parentMapsFd, mem,
+ __NR_socketcall, socketcall_req.call,
+ mem->pathname - (char*)mem + (char*)mem->self);
+ return true;
+ case SYS_RECVFROM:
+ // While we do not anticipate any particular need to receive data on
+ // unconnected sockets, there is no particular risk in doing so.
+ // Fall through
+ case SYS_RECV:
+ if (socketcall_req.args.recv.flags &
+ ~(MSG_DONTWAIT|MSG_OOB|MSG_PEEK|MSG_TRUNC|MSG_WAITALL)) {
+ // Unsupported flag encountered. Deny the call.
+ goto deny;
+ }
+ // Receiving data on a connected socket is similar to calling read().
+ // Allow it.
+ goto accept_complex;
+ case SYS_SHUTDOWN:
+ // Shutting down a socket is always OK.
+ goto accept_simple;
+ case SYS_SETSOCKOPT:
+ switch (socketcall_req.args.setsockopt.level) {
+ case SOL_SOCKET:
+ switch (socketcall_req.args.setsockopt.optname) {
+ case SO_KEEPALIVE:
+ case SO_LINGER:
+ case SO_OOBINLINE:
+ case SO_RCVBUF:
+ case SO_RCVLOWAT:
+ case SO_SNDLOWAT:
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ case SO_REUSEADDR:
+ case SO_SNDBUF:
+ case SO_TIMESTAMP:
+ goto accept_complex;
+ default:
+ break;
+ }
+ break;
+ case IPPROTO_TCP:
+ switch (socketcall_req.args.setsockopt.optname) {
+ case TCP_CORK:
+ case TCP_DEFER_ACCEPT:
+ case TCP_INFO:
+ case TCP_KEEPCNT:
+ case TCP_KEEPIDLE:
+ case TCP_KEEPINTVL:
+ case TCP_LINGER2:
+ case TCP_MAXSEG:
+ case TCP_NODELAY:
+ case TCP_QUICKACK:
+ case TCP_SYNCNT:
+ case TCP_WINDOW_CLAMP:
+ goto accept_complex;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ goto deny;
+ case SYS_GETSOCKOPT:
+ switch (socketcall_req.args.getsockopt.level) {
+ case SOL_SOCKET:
+ switch (socketcall_req.args.getsockopt.optname) {
+ case SO_ACCEPTCONN:
+ case SO_ERROR:
+ case SO_KEEPALIVE:
+ case SO_LINGER:
+ case SO_OOBINLINE:
+ case SO_RCVBUF:
+ case SO_RCVLOWAT:
+ case SO_SNDLOWAT:
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ case SO_REUSEADDR:
+ case SO_SNDBUF:
+ case SO_TIMESTAMP:
+ case SO_TYPE:
+ goto accept_complex;
+ default:
+ break;
+ }
+ break;
+ case IPPROTO_TCP:
+ switch (socketcall_req.args.getsockopt.optname) {
+ case TCP_CORK:
+ case TCP_DEFER_ACCEPT:
+ case TCP_INFO:
+ case TCP_KEEPCNT:
+ case TCP_KEEPIDLE:
+ case TCP_KEEPINTVL:
+ case TCP_LINGER2:
+ case TCP_MAXSEG:
+ case TCP_NODELAY:
+ case TCP_QUICKACK:
+ case TCP_SYNCNT:
+ case TCP_WINDOW_CLAMP:
+ goto accept_complex;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ goto deny;
+ case SYS_SENDMSG: {
+ struct msghdr* msg = reinterpret_cast<struct msghdr*>(extra);
+
+ if (sizeof(socketcall_req.args) + sizeof(*msg) + numSendmsgExtra >
+ sizeof(mem->pathname)) {
+ goto deny;
+ }
+
+ if (msg->msg_namelen ||
+ (socketcall_req.args.sendmsg.flags &
+ ~(MSG_CONFIRM|MSG_DONTWAIT|MSG_EOR|MSG_MORE|MSG_NOSIGNAL|MSG_OOB))){
+ goto deny;
+ }
+
+ // The trusted process receives file handles when a new untrusted thread
+ // gets created. We have security checks in place that prevent any
+ // critical information from being tampered with during thread creation.
+ // But if we disallowed passing of file handles, this would add an extra
+ // hurdle for an attacker.
+ // Unfortunately, for now, this is not possible as Chrome's
+ // base::SendRecvMsg() needs the ability to pass file handles.
+ if (msg->msg_controllen) {
+ msg->msg_control = sendmsgExtra + msg->msg_namelen;
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg);
+ do {
+ if (cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS) {
+ goto deny;
+ }
+ } while ((cmsg = CMSG_NXTHDR(msg, cmsg)) != NULL);
+ }
+
+ // This must be a locked system call, because we have to ensure that
+ // the untrusted code does not tamper with the msghdr after we have
+ // examined it.
+ SecureMem::lockSystemCall(parentMapsFd, mem);
+ socketcall_req.args.sendmsg.msg =
+ reinterpret_cast<struct msghdr*>(mem->pathname +
+ sizeof(socketcall_req.args) -
+ (char*)mem + (char*)mem->self);
+ memcpy(mem->pathname, &socketcall_req.args, sizeof(socketcall_req.args));
+ if (numSendmsgExtra) {
+ if (msg->msg_namelen > 0) {
+ msg->msg_name = const_cast<struct msghdr*>(
+ socketcall_req.args.sendmsg.msg) + 1;
+ }
+ if (msg->msg_controllen > 0) {
+ msg->msg_control = (char *)(
+ socketcall_req.args.sendmsg.msg + 1) + msg->msg_namelen;
+ }
+ memcpy(mem->pathname + sizeof(socketcall_req.args) + sizeof(*msg),
+ sendmsgExtra, numSendmsgExtra);
+ }
+ memcpy(mem->pathname + sizeof(socketcall_req.args), msg, sizeof(*msg));
+ SecureMem::sendSystemCall(threadFdPub, true, parentMapsFd, mem,
+ __NR_socketcall, socketcall_req.call,
+ mem->pathname - (char*)mem + (char*)mem->self);
+ return true;
+ }
+ case SYS_RECVMSG:
+ // Receiving messages is general not security critical.
+ if (socketcall_req.args.recvmsg.flags &
+ ~(MSG_DONTWAIT|MSG_OOB|MSG_PEEK|MSG_TRUNC|MSG_WAITALL)) {
+ goto deny;
+ }
+ goto accept_complex;
+ default:
+ deny:
+ SecureMem::abandonSystemCall(threadFd, rc);
+ return false;
+ }
+}
+
+#endif
+
+} // namespace