diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index de5092ee5ea..e1bdaba200d 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -36,6 +36,7 @@ import ( "golang.org/x/sys/unix" "google.golang.org/protobuf/proto" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/abi/linux/errno" "gvisor.dev/gvisor/pkg/context" @@ -2151,6 +2152,14 @@ func SetSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name i }) return nil + case linux.SO_ATTACH_FILTER: + if socket.IsTCP(s) && + !t.HasCapabilityIn(linux.CAP_NET_ADMIN, t.NetworkNamespace().UserNamespace()) { + return syserr.ErrNotPermitted + } + incrementBadSetSocketOptionMetric(t, &socketLevelSocketFieldValue, name) + return nil + case linux.SO_DETACH_FILTER: // optval is ignored. var v tcpip.SocketDetachFilterOption @@ -2188,7 +2197,6 @@ func SetSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name i linux.SO_BSDCOMPAT, linux.SO_PEERCRED, linux.SO_SNDLOWAT, - linux.SO_ATTACH_FILTER, linux.SO_PEERNAME, linux.SO_TIMESTAMP, linux.SO_ACCEPTCONN, diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 5532f54770f..6fd5698f0b0 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -4480,6 +4480,7 @@ cc_binary( linkstatic = 1, malloc = "//test/util:errno_safe_allocator", deps = select_gtest() + [ + "//test/util:capability_util", "//test/util:file_descriptor", "//test/util:posix_error", "//test/util:socket_util", diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 5caa0d6f751..fd4d8163012 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -38,6 +38,7 @@ #include "absl/status/statusor.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "test/util/capability_util.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" #include "test/util/socket_util.h" @@ -2297,9 +2298,14 @@ TEST_P(SimpleTcpSocketTest, SetSocketAttachDetachFilter) { .len = std::size(code), .filter = code, }; - ASSERT_THAT( - setsockopt(s.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)), - SyscallSucceeds()); + int ret = + setsockopt(s.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); + if (ret < 0 && errno == EPERM) { + // Linux 5d39580f68e6 ("tcp: restrict SO_ATTACH_FILTER to priv users") + // requires CAP_NET_ADMIN over the socket's network namespace. + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + } + ASSERT_THAT(ret, SyscallSucceeds()); constexpr int val = 0; ASSERT_THAT( @@ -2307,6 +2313,26 @@ TEST_P(SimpleTcpSocketTest, SetSocketAttachDetachFilter) { SyscallSucceeds()); } +TEST_P(SimpleTcpSocketTest, SetSocketAttachFilterWithoutNetAdmin) { + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + struct sock_filter code[] = {{0x6, 0, 0, 0x00040000}}; // ret 0x40000 + struct sock_fprog bpf = { + .len = std::size(code), + .filter = code, + }; + AutoCapability cap(CAP_NET_ADMIN, false); + int ret = + setsockopt(s.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); + if (!IsRunningOnGvisor() || IsRunningWithHostinet()) { + // Only Linux 5d39580f68e6 ("tcp: restrict SO_ATTACH_FILTER to priv + // users") and later requires CAP_NET_ADMIN, so this can succeed on + // old kernels (including with hostinet on an old kernel). + SKIP_IF(ret == 0); + } + EXPECT_THAT(ret, SyscallFailsWithErrno(EPERM)); +} + #endif // __linux__ TEST_P(SimpleTcpSocketTest, SetSocketDetachFilterNoInstalledFilter) {