diff options
Diffstat (limited to 'sandbox/linux/seccomp/socketcall.cc')
-rw-r--r-- | sandbox/linux/seccomp/socketcall.cc | 1039 |
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 |