diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp --- a/security/sandbox/linux/SandboxFilter.cpp +++ b/security/sandbox/linux/SandboxFilter.cpp @@ -128,10 +128,11 @@ // Subclasses can assign these in their constructors to loosen the // default settings. SandboxBrokerClient* mBroker = nullptr; bool mMayCreateShmem = false; bool mAllowUnsafeSocketPair = false; + bool mBrokeredConnect = false; // Can connect() be brokered? SandboxPolicyCommon() = default; typedef const sandbox::arch_seccomp_data& ArgsRef; @@ -533,10 +534,124 @@ MOZ_CRASH("unreachable?"); return -ENOSYS; #endif } + // This just needs to return something to stand in for the + // unconnected socket until ConnectTrap, below, and keep track of + // the socket type somehow. Half a socketpair *is* a socket, so it + // should result in minimal confusion in the caller. + static intptr_t FakeSocketTrapCommon(int domain, int type, int protocol) { + int fds[2]; + // X11 client libs will still try to getaddrinfo() even for a + // local connection. Also, WebRTC still has vestigial network + // code trying to do things in the content process. Politely tell + // them no. + if (domain != AF_UNIX) { + return -EAFNOSUPPORT; + } + if (socketpair(domain, type, protocol, fds) != 0) { + return -errno; + } + close(fds[1]); + return fds[0]; + } + + static intptr_t FakeSocketTrap(ArgsRef aArgs, void* aux) { + return FakeSocketTrapCommon(static_cast(aArgs.args[0]), + static_cast(aArgs.args[1]), + static_cast(aArgs.args[2])); + } + + static intptr_t FakeSocketTrapLegacy(ArgsRef aArgs, void* aux) { + const auto innerArgs = reinterpret_cast(aArgs.args[1]); + + return FakeSocketTrapCommon(static_cast(innerArgs[0]), + static_cast(innerArgs[1]), + static_cast(innerArgs[2])); + } + + static Maybe DoGetSockOpt(int fd, int optname) { + int optval; + socklen_t optlen = sizeof(optval); + + if (getsockopt(fd, SOL_SOCKET, optname, &optval, &optlen) != 0) { + return Nothing(); + } + MOZ_RELEASE_ASSERT(static_cast(optlen) == sizeof(optval)); + return Some(optval); + } + + // Substitute the newly connected socket from the broker for the + // original socket. This is meant to be used on a fd from + // FakeSocketTrap, above, but it should also work to simulate + // re-connect()ing a real connected socket. + // + // Warning: This isn't quite right if the socket is dup()ed, because + // other duplicates will still be the original socket, but hopefully + // nothing we're dealing with does that. + static intptr_t ConnectTrapCommon(SandboxBrokerClient* aBroker, int aFd, + const struct sockaddr_un* aAddr, + socklen_t aLen) { + if (aFd < 0) { + return -EBADF; + } + const auto maybeDomain = DoGetSockOpt(aFd, SO_DOMAIN); + if (!maybeDomain) { + return -errno; + } + if (*maybeDomain != AF_UNIX) { + return -EAFNOSUPPORT; + } + const auto maybeType = DoGetSockOpt(aFd, SO_TYPE); + if (!maybeType) { + return -errno; + } + const int oldFlags = fcntl(aFd, F_GETFL); + if (oldFlags == -1) { + return -errno; + } + const int newFd = aBroker->Connect(aAddr, aLen, *maybeType); + if (newFd < 0) { + return newFd; + } + // Copy over the nonblocking flag. The connect() won't be + // nonblocking in that case, but that shouldn't matter for + // AF_UNIX. The other fcntl-settable flags are either irrelevant + // for sockets (e.g., O_APPEND) or would be blocked by this + // seccomp-bpf policy, so they're ignored. + if (fcntl(newFd, F_SETFL, oldFlags & O_NONBLOCK) != 0) { + close(newFd); + return -errno; + } + if (dup2(newFd, aFd) < 0) { + close(newFd); + return -errno; + } + close(newFd); + return 0; + } + + static intptr_t ConnectTrap(ArgsRef aArgs, void* aux) { + typedef const struct sockaddr_un* AddrPtr; + + return ConnectTrapCommon(static_cast(aux), + static_cast(aArgs.args[0]), + reinterpret_cast(aArgs.args[1]), + static_cast(aArgs.args[2])); + } + + static intptr_t ConnectTrapLegacy(ArgsRef aArgs, void* aux) { + const auto innerArgs = reinterpret_cast(aArgs.args[1]); + typedef const struct sockaddr_un* AddrPtr; + + return ConnectTrapCommon(static_cast(aux), + static_cast(innerArgs[0]), + reinterpret_cast(innerArgs[1]), + static_cast(innerArgs[2])); + } + public: ResultExpr InvalidSyscall() const override { return Trap(BlockedSyscallTrap, nullptr); } @@ -630,15 +745,37 @@ return Some(Allow()); } Arg level(1), optname(2); // SO_SNDBUF is used by IPC to avoid constructing // unnecessarily large gather arrays for `sendmsg`. - return Some( - If(AllOf(level == SOL_SOCKET, optname == SO_SNDBUF), Allow()) - .Else(InvalidSyscall())); + // + // SO_DOMAIN and SO_TYPE are needed for connect() brokering, + // but they're harmless even when it's not enabled. + return Some(If(AllOf(level == SOL_SOCKET, + AnyOf(optname == SO_SNDBUF, optname == SO_DOMAIN, + optname == SO_TYPE)), + Allow()) + .Else(InvalidSyscall())); } + // These two cases are for connect() brokering, if enabled. + case SYS_SOCKET: + if (mBrokeredConnect) { + const auto trapFn = aHasArgs ? FakeSocketTrap : FakeSocketTrapLegacy; + MOZ_ASSERT(mBroker); + return Some(Trap(trapFn, mBroker)); + } + return Nothing(); + + case SYS_CONNECT: + if (mBrokeredConnect) { + const auto trapFn = aHasArgs ? ConnectTrap : ConnectTrapLegacy; + MOZ_ASSERT(mBroker); + return Some(Trap(trapFn, mBroker)); + } + return Nothing(); + default: return Nothing(); } } @@ -1006,10 +1143,16 @@ return If(AnyOf(request == TCGETS, request == TIOCGWINSZ), Error(ENOTTY)) .Else(SandboxPolicyBase::EvaluateSyscall(sysno)); } + CASES_FOR_dup2: // See ConnectTrapCommon + if (mBrokeredConnect) { + return Allow(); + } + return SandboxPolicyBase::EvaluateSyscall(sysno); + #ifdef MOZ_ASAN // ASAN's error reporter wants to know if stderr is a tty. case __NR_ioctl: { Arg fd(0); return If(fd == STDERR_FILENO, Error(ENOTTY)).Else(InvalidSyscall()); @@ -1093,133 +1236,20 @@ close(fd); return rv; } - // This just needs to return something to stand in for the - // unconnected socket until ConnectTrap, below, and keep track of - // the socket type somehow. Half a socketpair *is* a socket, so it - // should result in minimal confusion in the caller. - static intptr_t FakeSocketTrapCommon(int domain, int type, int protocol) { - int fds[2]; - // X11 client libs will still try to getaddrinfo() even for a - // local connection. Also, WebRTC still has vestigial network - // code trying to do things in the content process. Politely tell - // them no. - if (domain != AF_UNIX) { - return -EAFNOSUPPORT; - } - if (socketpair(domain, type, protocol, fds) != 0) { - return -errno; - } - close(fds[1]); - return fds[0]; - } - - static intptr_t FakeSocketTrap(ArgsRef aArgs, void* aux) { - return FakeSocketTrapCommon(static_cast(aArgs.args[0]), - static_cast(aArgs.args[1]), - static_cast(aArgs.args[2])); - } - - static intptr_t FakeSocketTrapLegacy(ArgsRef aArgs, void* aux) { - const auto innerArgs = reinterpret_cast(aArgs.args[1]); - - return FakeSocketTrapCommon(static_cast(innerArgs[0]), - static_cast(innerArgs[1]), - static_cast(innerArgs[2])); - } - - static Maybe DoGetSockOpt(int fd, int optname) { - int optval; - socklen_t optlen = sizeof(optval); - - if (getsockopt(fd, SOL_SOCKET, optname, &optval, &optlen) != 0) { - return Nothing(); - } - MOZ_RELEASE_ASSERT(static_cast(optlen) == sizeof(optval)); - return Some(optval); - } - - // Substitute the newly connected socket from the broker for the - // original socket. This is meant to be used on a fd from - // FakeSocketTrap, above, but it should also work to simulate - // re-connect()ing a real connected socket. - // - // Warning: This isn't quite right if the socket is dup()ed, because - // other duplicates will still be the original socket, but hopefully - // nothing we're dealing with does that. - static intptr_t ConnectTrapCommon(SandboxBrokerClient* aBroker, int aFd, - const struct sockaddr_un* aAddr, - socklen_t aLen) { - if (aFd < 0) { - return -EBADF; - } - const auto maybeDomain = DoGetSockOpt(aFd, SO_DOMAIN); - if (!maybeDomain) { - return -errno; - } - if (*maybeDomain != AF_UNIX) { - return -EAFNOSUPPORT; - } - const auto maybeType = DoGetSockOpt(aFd, SO_TYPE); - if (!maybeType) { - return -errno; - } - const int oldFlags = fcntl(aFd, F_GETFL); - if (oldFlags == -1) { - return -errno; - } - const int newFd = aBroker->Connect(aAddr, aLen, *maybeType); - if (newFd < 0) { - return newFd; - } - // Copy over the nonblocking flag. The connect() won't be - // nonblocking in that case, but that shouldn't matter for - // AF_UNIX. The other fcntl-settable flags are either irrelevant - // for sockets (e.g., O_APPEND) or would be blocked by this - // seccomp-bpf policy, so they're ignored. - if (fcntl(newFd, F_SETFL, oldFlags & O_NONBLOCK) != 0) { - close(newFd); - return -errno; - } - if (dup2(newFd, aFd) < 0) { - close(newFd); - return -errno; - } - close(newFd); - return 0; - } - - static intptr_t ConnectTrap(ArgsRef aArgs, void* aux) { - typedef const struct sockaddr_un* AddrPtr; - - return ConnectTrapCommon(static_cast(aux), - static_cast(aArgs.args[0]), - reinterpret_cast(aArgs.args[1]), - static_cast(aArgs.args[2])); - } - - static intptr_t ConnectTrapLegacy(ArgsRef aArgs, void* aux) { - const auto innerArgs = reinterpret_cast(aArgs.args[1]); - typedef const struct sockaddr_un* AddrPtr; - - return ConnectTrapCommon(static_cast(aux), - static_cast(innerArgs[0]), - reinterpret_cast(innerArgs[1]), - static_cast(innerArgs[2])); - } - public: ContentSandboxPolicy(SandboxBrokerClient* aBroker, ContentProcessSandboxParams&& aParams) : mParams(std::move(aParams)), mAllowSysV(PR_GetEnv("MOZ_SANDBOX_ALLOW_SYSV") != nullptr), mUsingRenderDoc(PR_GetEnv("RENDERDOC_CAPTUREOPTS") != nullptr) { mBroker = aBroker; mMayCreateShmem = true; mAllowUnsafeSocketPair = true; + mBrokeredConnect = true; } ~ContentSandboxPolicy() override = default; Maybe EvaluateSocketCall(int aCall, @@ -1232,18 +1262,16 @@ #ifdef ANDROID case SYS_SOCKET: return Some(Error(EACCES)); #else // #ifdef DESKTOP - case SYS_SOCKET: { - const auto trapFn = aHasArgs ? FakeSocketTrap : FakeSocketTrapLegacy; - return Some(AllowBelowLevel(4, Trap(trapFn, nullptr))); - } - case SYS_CONNECT: { - const auto trapFn = aHasArgs ? ConnectTrap : ConnectTrapLegacy; - return Some(AllowBelowLevel(4, Trap(trapFn, mBroker))); - } + case SYS_SOCKET: + case SYS_CONNECT: + if (BelowLevel(4)) { + return Some(Allow()); + } + return SandboxPolicyCommon::EvaluateSocketCall(aCall, aHasArgs); case SYS_RECV: case SYS_SEND: case SYS_GETSOCKOPT: case SYS_SETSOCKOPT: case SYS_GETSOCKNAME: @@ -1458,13 +1486,10 @@ case __NR_getrusage: case __NR_times: return Allow(); - CASES_FOR_dup2: // See ConnectTrapCommon - return Allow(); - case __NR_fsync: case __NR_msync: return Allow(); case __NR_getpriority: