Skip to content

Commit 2672dc8

Browse files
shailend-ggvisor-bot
authored andcommitted
Fix a bug around IFLA_NET_NS_FD
IFLA_NET_NS_FD is supplied in an RTM_SETLINK message from userland to move a link to a new netns. The same message might mutate other attributes of the link. Thus far we were applying those changes to a different link in the source netns. PiperOrigin-RevId: 834406670
1 parent e3403be commit 2672dc8

File tree

5 files changed

+137
-40
lines changed

5 files changed

+137
-40
lines changed

pkg/sentry/socket/netstack/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package(
99
declare_mutex(
1010
name = "netstack_link_mutex",
1111
out = "netstack_link_mutex.go",
12+
nested_lock_names = ["destStack"],
1213
package = "netstack",
1314
prefix = "netstackLink",
1415
)

pkg/sentry/socket/netstack/stack.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er
229229

230230
// precondition: s.linkLock is held.
231231
func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
232-
oldNicInfo, ok := s.Stack.SingleNICInfo(id)
232+
home := s
233+
changed := false
234+
oldNicInfo, ok := home.Stack.SingleNICInfo(id)
233235
if !ok {
234236
return syserr.ErrUnknownNICID
235237
}
@@ -250,35 +252,46 @@ func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map
250252
}
251253
defer ns.DecRef(ctx)
252254
peer := ns.Stack().(*Stack)
253-
if peer.Stack != s.Stack {
255+
if peer.Stack != home.Stack {
254256
var err tcpip.Error
255257
oldID := id
256258

257-
id, err = s.Stack.SetNICStack(id, peer.Stack)
259+
peer.linkMu.NestedLock(netstackLinkLockDeststack)
260+
defer peer.linkMu.NestedUnlock(netstackLinkLockDeststack)
261+
262+
id, err = home.Stack.SetNICStack(id, peer.Stack)
258263
if err != nil {
259264
return syserr.TranslateNetstackError(err)
260265
}
261266

262-
s.sendDeleteEvent(ctx, oldID, oldNicInfo) // inform about exit from old ns
263-
peer.sendChangeEvent(ctx, id) // inform about entry into new ns
267+
home.sendDeleteEvent(ctx, oldID, oldNicInfo) // inform about exit from old ns
268+
changed = true // inform about entry into new ns
269+
270+
oldNicInfo, ok = peer.Stack.SingleNICInfo(id)
271+
if !ok {
272+
// Because we hold peer.linkMu, this should never happen.
273+
ctx.Warningf("Newly rehomed NIC %d not found in new stack %p", id, home.Stack)
274+
return syserr.ErrUnknownNICID
275+
}
276+
home = peer
277+
264278
// TODO: Once we support IFLA_LINK_NETNSID, we need to call sendChangeEvent on
265279
// the peer interface if this interface is part of a veth pair.
266280
}
267281
}
268282

269-
changed := false
270283
for t, v := range linkAttrs {
271284
switch t {
272285
case linux.IFLA_MASTER:
273286
master, ok := v.Uint32()
274287
if !ok {
275288
return syserr.ErrInvalidArgument
276289
}
277-
if mid, ok := s.Stack.GetNICCoordinatorID(id); ok && mid == tcpip.NICID(master) {
290+
if mid, ok := home.Stack.GetNICCoordinatorID(id); ok && mid == tcpip.NICID(master) {
278291
continue
279292
}
280293
if master != 0 {
281-
if err := s.Stack.SetNICCoordinator(id, tcpip.NICID(master)); err != nil {
294+
if err := home.Stack.SetNICCoordinator(id, tcpip.NICID(master)); err != nil {
282295
return syserr.TranslateNetstackError(err)
283296
}
284297
changed = true
@@ -291,15 +304,15 @@ func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map
291304
if oldNicInfo.LinkAddress == addr {
292305
continue
293306
}
294-
if err := s.Stack.SetNICAddress(id, addr); err != nil {
307+
if err := home.Stack.SetNICAddress(id, addr); err != nil {
295308
return syserr.TranslateNetstackError(err)
296309
}
297310
changed = true
298311
case linux.IFLA_IFNAME:
299312
if oldNicInfo.Name == v.String() {
300313
continue
301314
}
302-
if err := s.Stack.SetNICName(id, v.String()); err != nil {
315+
if err := home.Stack.SetNICName(id, v.String()); err != nil {
303316
return syserr.TranslateNetstackError(err)
304317
}
305318
changed = true
@@ -311,7 +324,7 @@ func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map
311324
if oldNicInfo.MTU == mtu {
312325
continue
313326
}
314-
if err := s.Stack.SetNICMTU(id, mtu); err != nil {
327+
if err := home.Stack.SetNICMTU(id, mtu); err != nil {
315328
return syserr.TranslateNetstackError(err)
316329
}
317330
changed = true
@@ -321,7 +334,7 @@ func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map
321334
}
322335

323336
if changed {
324-
s.sendChangeEvent(ctx, id)
337+
home.sendChangeEvent(ctx, id)
325338
}
326339
return nil
327340
}

test/syscalls/linux/socket_netlink_route.cc

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,16 @@ TEST(NetlinkRouteTest, VethAdd) {
17961796
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
17971797
SKIP_IF(IsRunningWithHostinet());
17981798

1799+
// Running the test in an ephemeral netns allows it to not interfere with
1800+
// other tests that also want to create veth pairs with the same names.
1801+
const FileDescriptor curr_nsfd =
1802+
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY));
1803+
Cleanup restore_netns = Cleanup([&] {
1804+
ASSERT_THAT(setns(curr_nsfd.get(), CLONE_NEWNET),
1805+
SyscallSucceedsWithValue(0));
1806+
});
1807+
ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0));
1808+
17991809
FileDescriptor fd =
18001810
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
18011811
VethRequest req = GetVethRequest(kSeq, "veth1", "veth2");
@@ -1823,6 +1833,86 @@ TEST(NetlinkRouteTest, LookupAllAddrOrder) {
18231833
}
18241834
}
18251835

1836+
struct NetNSRequest {
1837+
struct nlmsghdr hdr;
1838+
struct ifinfomsg ifm;
1839+
char buf[1024];
1840+
};
1841+
1842+
struct NetNSRequest GetNetNSRequest(uint32_t seq, int if_index, int ns_fd) {
1843+
struct NetNSRequest req = {};
1844+
1845+
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
1846+
req.hdr.nlmsg_type = RTM_NEWLINK;
1847+
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
1848+
req.hdr.nlmsg_seq = seq;
1849+
req.ifm.ifi_family = AF_UNSPEC;
1850+
req.ifm.ifi_index = if_index;
1851+
addattr(&req.hdr, sizeof(req), IFLA_NET_NS_FD, &ns_fd, sizeof(ns_fd));
1852+
1853+
return req;
1854+
}
1855+
1856+
// Guard against b/456552490, a bug where gvisor'd try to modify the given
1857+
// attributes in the wrong stack when the request also directs it to change the
1858+
// netns.
1859+
TEST(NetlinkRouteTest, ChangeNetnsAndOtherAttrsTogether) {
1860+
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
1861+
SKIP_IF(IsRunningWithHostinet());
1862+
// TODO(gvisor.dev/issue/4595): enable cooperative save tests.
1863+
const DisableSave ds;
1864+
1865+
// Running the test in an ephemeral netns allows it to not interfere with
1866+
// other tests that also want to create veth pairs with the same names.
1867+
const FileDescriptor curr_nsfd =
1868+
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY));
1869+
Cleanup restore_netns = Cleanup([&] {
1870+
ASSERT_THAT(setns(curr_nsfd.get(), CLONE_NEWNET),
1871+
SyscallSucceedsWithValue(0));
1872+
});
1873+
ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0));
1874+
1875+
FileDescriptor nlsk =
1876+
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
1877+
VethRequest req = GetVethRequest(kSeq, "veth1", "veth2");
1878+
EXPECT_NO_ERRNO(
1879+
NetlinkRequestAckOrError(nlsk, kSeq, &req, req.hdr.nlmsg_len));
1880+
int inner_veth_idx = if_nametoindex("veth2");
1881+
ASSERT_NE(inner_veth_idx, 0);
1882+
1883+
// Enter a new network namespace and move veth2 into it.
1884+
ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0));
1885+
const FileDescriptor inner_nsfd =
1886+
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY));
1887+
NetNSRequest set_netns_req =
1888+
GetNetNSRequest(kSeq, inner_veth_idx, inner_nsfd.get());
1889+
// Request a name change along with the netns change.
1890+
std::string new_name = "veth2_new";
1891+
addattr(&set_netns_req.hdr, sizeof(set_netns_req), IFLA_IFNAME,
1892+
new_name.c_str(), new_name.size());
1893+
EXPECT_NO_ERRNO(NetlinkRequestAckOrError(nlsk, kSeq, &set_netns_req,
1894+
set_netns_req.hdr.nlmsg_len));
1895+
1896+
// Verify that an interface with the new name exists in the inner netns.
1897+
bool found_name_inner = false;
1898+
std::vector<Link> links = ASSERT_NO_ERRNO_AND_VALUE(DumpLinks());
1899+
for (const Link& link : links) {
1900+
if (link.name == new_name) {
1901+
found_name_inner = true;
1902+
break;
1903+
}
1904+
}
1905+
EXPECT_TRUE(found_name_inner)
1906+
<< "Did not find interface with name " << new_name;
1907+
1908+
// And verify also that the outer netns does not have the new name.
1909+
links = ASSERT_NO_ERRNO_AND_VALUE(DumpLinks(nlsk));
1910+
for (const Link& link : links) {
1911+
ASSERT_NE(link.name, new_name)
1912+
<< "Found interface with name " << new_name << " in outer netns";
1913+
}
1914+
}
1915+
18261916
struct NameRequest {
18271917
struct nlmsghdr hdr;
18281918
struct ifinfomsg ifm;
@@ -1965,27 +2055,23 @@ TEST(NetlinkRouteTest, LinkMulticastGroupBasic) {
19652055
}
19662056
}
19672057

1968-
struct VethRequest GetSetNetNSRequest(uint32_t seq, int if_index, int ns_fd) {
1969-
struct VethRequest req = {};
1970-
1971-
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
1972-
req.hdr.nlmsg_type = RTM_NEWLINK;
1973-
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
1974-
req.hdr.nlmsg_seq = seq;
1975-
req.ifm.ifi_family = AF_UNSPEC;
1976-
req.ifm.ifi_index = if_index;
1977-
addattr(&req.hdr, sizeof(req), IFLA_NET_NS_FD, &ns_fd, sizeof(ns_fd));
1978-
1979-
return req;
1980-
}
1981-
19822058
// To verify the namespaced nature of the netlink multicast groups.
19832059
TEST(NetlinkRouteTest, LinkMulticastGroupNamespaced) {
19842060
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
19852061
SKIP_IF(IsRunningWithHostinet());
19862062
// TODO(gvisor.dev/issue/4595): enable cooperative save tests.
19872063
const DisableSave ds;
19882064

2065+
// Running the test in an ephemeral netns allows it to not interfere with
2066+
// other tests that also want to create veth pairs with the same names.
2067+
const FileDescriptor curr_nsfd =
2068+
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY));
2069+
Cleanup restore_netns = Cleanup([&] {
2070+
ASSERT_THAT(setns(curr_nsfd.get(), CLONE_NEWNET),
2071+
SyscallSucceedsWithValue(0));
2072+
});
2073+
ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0));
2074+
19892075
FileDescriptor control_nlsk =
19902076
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
19912077
VethRequest req = GetVethRequest(kSeq, "veth1", "veth2");
@@ -1998,16 +2084,9 @@ TEST(NetlinkRouteTest, LinkMulticastGroupNamespaced) {
19982084
struct sockaddr_nl mcast_addr = {};
19992085
mcast_addr.nl_family = AF_NETLINK;
20002086
mcast_addr.nl_groups = RTMGRP_LINK;
2001-
FileDescriptor root_nlsk =
2087+
FileDescriptor outer_nlsk =
20022088
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE, &mcast_addr));
20032089

2004-
const FileDescriptor root_nsfd =
2005-
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY));
2006-
Cleanup restore_netns = Cleanup([&] {
2007-
ASSERT_THAT(setns(root_nsfd.get(), CLONE_NEWNET),
2008-
SyscallSucceedsWithValue(0));
2009-
});
2010-
20112090
// Enter a new network namespace.
20122091
ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0));
20132092
FileDescriptor inner_nlsk =
@@ -2016,24 +2095,24 @@ TEST(NetlinkRouteTest, LinkMulticastGroupNamespaced) {
20162095
// And move veth2 into it.
20172096
const FileDescriptor inner_nsfd =
20182097
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY));
2019-
VethRequest set_netns_req =
2020-
GetSetNetNSRequest(kSeq, inner_veth_idx, inner_nsfd.get());
2098+
NetNSRequest set_netns_req =
2099+
GetNetNSRequest(kSeq, inner_veth_idx, inner_nsfd.get());
20212100
EXPECT_NO_ERRNO(NetlinkRequestAckOrError(control_nlsk, kSeq, &set_netns_req,
20222101
set_netns_req.hdr.nlmsg_len));
20232102

20242103
constexpr int kPollTimeoutMs = 1000;
20252104
bool got_msg = false;
2026-
// We expect an RTM_DELINK message for veth2 in the root netns socket.
2105+
// We expect an RTM_DELINK message for veth2 in the outer netns socket.
20272106
// But an RTM_NEWLINK is also expected for veth1 because its peer was moved.
20282107
// Hence the two attempts. N.B. gVisor does not send the RTM_NEWLINK because
20292108
// IFLA_LINK_NETNSID is not yet supported.
20302109
for (int i = 0; i < 2; ++i) {
2031-
struct pollfd pfd = {.fd = root_nlsk.get(), .events = POLLIN};
2110+
struct pollfd pfd = {.fd = outer_nlsk.get(), .events = POLLIN};
20322111
ASSERT_EQ(RetryEINTR(poll)(&pfd, 1, kPollTimeoutMs), 1)
20332112
<< "root_nlsk: Did not get veth2 DELLINK";
20342113

20352114
ASSERT_NO_ERRNO(NetlinkResponse(
2036-
root_nlsk,
2115+
outer_nlsk,
20372116
[&](const struct nlmsghdr* hdr) {
20382117
const struct ifinfomsg* msg =
20392118
reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
@@ -2044,7 +2123,7 @@ TEST(NetlinkRouteTest, LinkMulticastGroupNamespaced) {
20442123
/*expect_nlmsgerr=*/false));
20452124
if (got_msg) break;
20462125
}
2047-
EXPECT_TRUE(got_msg) << "root_nlsk: Did not get veth2 DELLINK";
2126+
EXPECT_TRUE(got_msg) << "outer_nlsk: Did not get veth2 DELLINK";
20482127

20492128
// We expect an RTM_NEWLINK message for veth2 in the inner netns socket.
20502129
{

test/syscalls/linux/socket_netlink_route_util.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,10 @@ PosixError DumpLinks(
261261

262262
PosixErrorOr<std::vector<Link>> DumpLinks() {
263263
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
264+
return DumpLinks(fd);
265+
}
264266

267+
PosixErrorOr<std::vector<Link>> DumpLinks(const FileDescriptor& fd) {
265268
std::vector<Link> links;
266269
RETURN_IF_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) {
267270
if (hdr->nlmsg_type != RTM_NEWLINK ||

test/syscalls/linux/socket_netlink_route_util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ PosixError DumpLinks(const FileDescriptor& fd, uint32_t seq,
3838
const std::function<void(const struct nlmsghdr* hdr)>& fn);
3939

4040
PosixErrorOr<std::vector<Link>> DumpLinks();
41+
PosixErrorOr<std::vector<Link>> DumpLinks(const FileDescriptor& fd);
4142

4243
// Returns the loopback link on the system. ENOENT if not found.
4344
PosixErrorOr<Link> LoopbackLink();

0 commit comments

Comments
 (0)