From beaecd1e3cb00e1766e498f613adaaabe1f5ac37 Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Tue, 21 Dec 2021 15:00:16 -0800 Subject: [PATCH] Support SOL_ICMPV6 -> ICMPV6_FILTER PiperOrigin-RevId: 417696519 --- pkg/abi/linux/ip.go | 5 + pkg/abi/linux/socket.go | 8 ++ pkg/sentry/socket/netstack/netstack.go | 69 ++++++++++- pkg/tcpip/tcpip.go | 23 ++++ pkg/tcpip/transport/raw/endpoint.go | 60 ++++++++- test/syscalls/linux/raw_socket_icmp.cc | 162 ++++++++++++++++++++++++- 6 files changed, 318 insertions(+), 9 deletions(-) diff --git a/pkg/abi/linux/ip.go b/pkg/abi/linux/ip.go index ef6d1093e..df94e150b 100644 --- a/pkg/abi/linux/ip.go +++ b/pkg/abi/linux/ip.go @@ -159,3 +159,8 @@ const ( IPV6_RECVFRAGSIZE = 77 IPV6_FREEBIND = 78 ) + +// Socket options from uapi/linux/icmpv6.h +const ( + ICMPV6_FILTER = 1 +) diff --git a/pkg/abi/linux/socket.go b/pkg/abi/linux/socket.go index a31690a04..5cc909b97 100644 --- a/pkg/abi/linux/socket.go +++ b/pkg/abi/linux/socket.go @@ -586,3 +586,11 @@ const SCM_MAX_FD = 253 // socket option for querying whether a socket is in a listening // state. const SO_ACCEPTCON = 1 << 16 + +// ICMP6Filter represents struct icmp6_filter from linux/icmpv6.h. +// +// +marshal +// +stateify savable +type ICMP6Filter struct { + Filter [8]uint32 +} diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index a1e293499..ee9195169 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -65,6 +65,8 @@ import ( "gvisor.dev/gvisor/pkg/waiter" ) +const bitsPerUint32 = 32 + func mustCreateMetric(name, description string) *tcpip.StatCounter { var cm tcpip.StatCounter metric.MustRegisterCustomUint64Metric(name, true /* cumulative */, false /* sync */, description, cm.Value) @@ -858,8 +860,10 @@ func GetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, family in case linux.SOL_IP: return getSockOptIP(t, s, ep, name, outPtr, outLen, family) + case linux.SOL_ICMPV6: + return getSockOptICMPv6(t, s, ep, name, outLen) + case linux.SOL_UDP, - linux.SOL_ICMPV6, linux.SOL_RAW, linux.SOL_PACKET: @@ -1293,6 +1297,39 @@ func getSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name, return nil, syserr.ErrProtocolNotAvailable } +func getSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outLen int) (marshal.Marshallable, *syserr.Error) { + if _, ok := ep.(tcpip.Endpoint); !ok { + log.Warningf("SOL_ICMPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name) + return nil, syserr.ErrUnknownProtocolOption + } + + if family, _, _ := s.Type(); family != linux.AF_INET6 { + return nil, syserr.ErrNotSupported + } + + switch name { + case linux.ICMPV6_FILTER: + var v tcpip.ICMPv6Filter + if err := ep.GetSockOpt(&v); err != nil { + return nil, syserr.TranslateNetstackError(err) + } + + filter := linux.ICMP6Filter{Filter: v.DenyType} + + // Linux truncates the output to outLen. + buf := t.CopyScratchBuffer(filter.SizeBytes()) + filter.MarshalUnsafe(buf) + if len(buf) > outLen { + buf = buf[:outLen] + } + bufP := primitive.ByteSlice(buf) + return &bufP, nil + default: + t.Kernel().EmitUnimplementedEvent(t) + } + return nil, syserr.ErrProtocolNotAvailable +} + // getSockOptIPv6 implements GetSockOpt when level is SOL_IPV6. func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { if _, ok := ep.(tcpip.Endpoint); !ok { @@ -1686,6 +1723,9 @@ func SetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, level int case linux.SOL_TCP: return setSockOptTCP(t, s, ep, name, optVal) + case linux.SOL_ICMPV6: + return setSockOptICMPv6(t, s, ep, name, optVal) + case linux.SOL_IPV6: return setSockOptIPv6(t, s, ep, name, optVal) @@ -1700,7 +1740,6 @@ func SetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, level int return syserr.ErrProtocolNotAvailable case linux.SOL_UDP, - linux.SOL_ICMPV6, linux.SOL_RAW: t.Kernel().EmitUnimplementedEvent(t) @@ -2051,6 +2090,32 @@ func setSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name i return nil } +func setSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { + if _, ok := ep.(tcpip.Endpoint); !ok { + log.Warningf("SOL_ICMPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name) + return syserr.ErrUnknownProtocolOption + } + + if family, _, _ := s.Type(); family != linux.AF_INET6 { + return syserr.ErrUnknownProtocolOption + } + + switch name { + case linux.ICMPV6_FILTER: + var req linux.ICMP6Filter + if len(optVal) < req.SizeBytes() { + return syserr.ErrInvalidArgument + } + + req.UnmarshalUnsafe(optVal) + return syserr.TranslateNetstackError(ep.SetSockOpt(&tcpip.ICMPv6Filter{DenyType: req.Filter})) + default: + t.Kernel().EmitUnimplementedEvent(t) + } + + return nil +} + // setSockOptIPv6 implements SetSockOpt when level is SOL_IPV6. func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { if _, ok := ep.(tcpip.Endpoint); !ok { diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index b7616db93..dd30a6519 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -897,6 +897,29 @@ type SettableSocketOption interface { isSettableSocketOption() } +// ICMPv6Filter specifes a filter for ICMPv6 types. +// +// +stateify savable +type ICMPv6Filter struct { + // DenyType indicates if an ICMP type should be blocked. + // + // The ICMPv6 type field is 8 bits so there are up to 256 different ICMPv6 + // types. + DenyType [8]uint32 +} + +// ShouldDeny returns true iff the ICMPv6 Type should be denied. +func (f *ICMPv6Filter) ShouldDeny(icmpType uint8) bool { + const bitsInUint32 = 32 + i := icmpType / bitsInUint32 + b := icmpType % bitsInUint32 + return f.DenyType[i]&(1< #include #include #include @@ -34,6 +35,15 @@ namespace testing { namespace { +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::FieldsAre; +using ::testing::Not; +using ::testing::Test; +using ::testing::Values; +using ::testing::WithParamInterface; + // The size of an empty ICMP packet and IP header together. constexpr size_t kEmptyICMPSize = 28; @@ -41,7 +51,7 @@ constexpr size_t kEmptyICMPSize = 28; // responds to ICMP echo requests, and thus a single echo request sent via // loopback leads to 2 received ICMP packets. -class RawSocketICMPTest : public ::testing::Test { +class RawSocketICMPTest : public Test { protected: // Creates a socket to be used in tests. void SetUp() override; @@ -109,6 +119,16 @@ TEST_F(RawSocketICMPTest, SockOptIPv6Checksum) { EXPECT_EQ(len, sizeof(v)); } +TEST_F(RawSocketICMPTest, ICMPv6FilterNotSupported) { + icmp6_filter v; + EXPECT_THAT(setsockopt(s_, SOL_ICMPV6, ICMP6_FILTER, &v, sizeof(v)), + SyscallFailsWithErrno(ENOPROTOOPT)); + socklen_t len = sizeof(v); + EXPECT_THAT(getsockopt(s_, SOL_ICMPV6, ICMP6_FILTER, &v, &len), + SyscallFailsWithErrno(EOPNOTSUPP)); + EXPECT_EQ(len, sizeof(v)); +} + // We'll only read an echo in this case, as the kernel won't respond to the // malformed ICMP checksum. TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) { @@ -552,6 +572,146 @@ void RawSocketICMPTest::ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len, SyscallSucceedsWithValue(expected_size + sizeof(struct iphdr))); } +class RawSocketICMPv6Test : public Test { + public: + void SetUp() override { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); + + fd_ = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6)); + } + + void TearDown() override { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { + return; + } + + EXPECT_THAT(close(fd_.release()), SyscallSucceeds()); + } + + protected: + const FileDescriptor& fd() { return fd_; } + + private: + FileDescriptor fd_; +}; + +TEST_F(RawSocketICMPv6Test, InitialFilterPassesAll) { + icmp6_filter got_filter; + socklen_t got_filter_len = sizeof(got_filter); + ASSERT_THAT(getsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter, + &got_filter_len), + SyscallSucceeds()); + ASSERT_EQ(got_filter_len, sizeof(got_filter)); + icmp6_filter expected_filter; + ICMP6_FILTER_SETPASSALL(&expected_filter); + EXPECT_THAT(got_filter, + FieldsAre(ElementsAreArray(expected_filter.icmp6_filt))); +} + +TEST_F(RawSocketICMPv6Test, GetPartialFilterSucceeds) { + icmp6_filter set_filter; + ICMP6_FILTER_SETBLOCKALL(&set_filter); + ASSERT_THAT(setsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter, + sizeof(set_filter)), + SyscallSucceeds()); + + icmp6_filter got_filter = {}; + // We use a length smaller than a full filter length and expect that + // only the bytes up to the provided length are modified. The last element + // should be unmodified when getsockopt returns. + constexpr socklen_t kShortFilterLen = + sizeof(got_filter) - sizeof(got_filter.icmp6_filt[0]); + socklen_t got_filter_len = kShortFilterLen; + ASSERT_THAT(getsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter, + &got_filter_len), + SyscallSucceeds()); + ASSERT_EQ(got_filter_len, kShortFilterLen); + icmp6_filter expected_filter = set_filter; + expected_filter.icmp6_filt[std::size(expected_filter.icmp6_filt) - 1] = 0; + EXPECT_THAT(got_filter, + FieldsAre(ElementsAreArray(expected_filter.icmp6_filt))); +} + +class RawSocketICMPv6TypeTest : public RawSocketICMPv6Test, + public WithParamInterface {}; + +TEST_P(RawSocketICMPv6TypeTest, FilterDeliveredPackets) { + const sockaddr_in6 addr = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_LOOPBACK_INIT, + }; + + const uint8_t allowed_type = GetParam(); + + // Pass only the allowed type. + { + icmp6_filter set_filter; + ICMP6_FILTER_SETBLOCKALL(&set_filter); + ICMP6_FILTER_SETPASS(allowed_type, &set_filter); + ASSERT_THAT(setsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter, + sizeof(set_filter)), + SyscallSucceeds()); + + icmp6_filter got_filter; + socklen_t got_filter_len = sizeof(got_filter); + ASSERT_THAT(getsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter, + &got_filter_len), + SyscallSucceeds()); + ASSERT_EQ(got_filter_len, sizeof(got_filter)); + EXPECT_THAT(got_filter, FieldsAre(ElementsAreArray(set_filter.icmp6_filt))); + } + + // Send an ICMP packet for each type. + uint8_t icmp_type = 0; + constexpr uint8_t kUnusedICMPCode = 0; + do { + const icmp6_hdr packet = { + .icmp6_type = icmp_type, + .icmp6_code = kUnusedICMPCode, + // The stack will calculate the checksum. + .icmp6_cksum = 0, + }; + + ASSERT_THAT(RetryEINTR(sendto)(fd().get(), &packet, sizeof(packet), 0, + reinterpret_cast(&addr), + sizeof(addr)), + SyscallSucceedsWithValue(sizeof(packet))); + } while (icmp_type++ != std::numeric_limits::max()); + + // Make sure only the allowed type was received. + { + icmp6_hdr got_packet; + sockaddr_in6 sender; + socklen_t sender_len = sizeof(sender); + ASSERT_THAT(RetryEINTR(recvfrom)( + fd().get(), &got_packet, sizeof(got_packet), 0 /* flags */, + reinterpret_cast(&sender), &sender_len), + SyscallSucceedsWithValue(sizeof(got_packet))); + ASSERT_EQ(sender_len, sizeof(sender)); + EXPECT_EQ(memcmp(&sender, &addr, sizeof(addr)), 0); + // The stack should have populated the checksum. + if (IsRunningOnGvisor() && !IsRunningWithHostinet()) { + // TODO(https://github.com/google/gvisor/pull/6957): Use same check as + // Linux. + EXPECT_THAT(got_packet, + FieldsAre(allowed_type, kUnusedICMPCode, 0 /* icmp6_cksum */, + _ /* icmp6_dataun */ + )); + } else { + EXPECT_THAT(got_packet, + FieldsAre(allowed_type, kUnusedICMPCode, + Not(0) /* icmp6_cksum */, _ /* icmp6_dataun */ + )); + } + EXPECT_THAT(got_packet.icmp6_data32, ElementsAre(0)); + } +} + +INSTANTIATE_TEST_SUITE_P(AllRawSocketTests, RawSocketICMPv6TypeTest, + Values(uint8_t{0}, + std::numeric_limits::max())); + } // namespace } // namespace testing