Re: [PATCH net-next 6/6] selftests/vsock: test the assign ioctl privilege checks
From: netdev-bot+sashiko@kernel.org
Date: 2026-09-06 17:04:07
Also in:
kvm, linux-doc, linux-kselftest, lkml, netdev
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: Critical: 0 · High: 0 · Medium: 2 · Low: 1 - [Medium] test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails() (tools/testing/selftests/vsock/vmtest.sh:1835-1863) runs the helper as… - [Medium] Both new tests equate a remote command exit status of 1 with 'the kernel returned -EPERM from IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS'. - [Low] Both new shared-VM tests (vmtest.sh:1820-1833 and 1835-1863) are added to USE_SHARED_VM but omit the `vm_reset_g2h` teardown that every… -- selftests/vsock: test the assign ioctl privilege checks This adds two vmtest.sh cases around IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS on /dev/vsock: one drops CAP_NET_ADMIN via setpriv, the other runs the helper as an unprivileged uid inside a new user plus net namespace. Both expect the helper to exit with the ioctl's errno, and CONFIG_USER_NS is added to the config. No Link: tags are present in the commit header.
quoted hunk ↗ jump to hunk
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh index 53591fa07f1a..efb94d17d997 100755 --- a/tools/testing/selftests/vsock/vmtest.sh +++ b/tools/testing/selftests/vsock/vmtest.sh@@ -28,6 +28,7 @@ readonly WAIT_PERIOD=3 readonly WAIT_PERIOD_MAX=60 readonly WAIT_QEMU=5 readonly PIDFILE_TEMPLATE=/tmp/vsock_vmtest_XXXX.pid +readonly EPERM=1 declare -A PIDFILES
[ ... ]
quoted hunk ↗ jump to hunk
@@ -333,7 +344,8 @@ check_args() { } check_deps() { - for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare; do + for dep in vng ${QEMU} busybox pkill ssh ss socat nsenter unshare \ + setpriv; do if [[ ! -x $(command -v "${dep}") ]]; then echo -e "skip: dependency ${dep} not found!\n" exit "${KSFT_SKIP}"
[ ... ]
quoted hunk ↗ jump to hunk
@@ -1805,6 +1817,51 @@ test_ns_guest_assign_g2h_netns_reset_on_ns_delete_ok() { return "${KSFT_PASS}" } +test_ns_guest_assign_g2h_netns_no_cap_net_admin_fails() { + local cmd="unshare -n setpriv --bounding-set=-net_admin" + local rc + + vm_ssh "init_ns" -- "${cmd}" ./vsock_assign_g2h_netns &>/dev/null + rc=$? + + if [[ "${rc}" -ne "${EPERM}" ]]; then + log_host "expected EPERM (${EPERM}) without CAP_NET_ADMIN, got ${rc}" + return "${KSFT_FAIL}" + fi + + return "${KSFT_PASS}" +}
[Severity: Medium]
Can this test pass without ever issuing the ioctl?
The helper turns errno into its own exit status:
tools/testing/selftests/vsock/vsock_assign_g2h_netns.c:main() {
ret = ioctl(fd, IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS);
if (ret < 0) {
ret = errno;
...
return ret;
}
so "the ioctl returned -EPERM" is encoded as exit status 1, which is also
the generic EXIT_FAILURE of the two wrappers that are prepended here. If
util-linux unshare cannot create the network namespace, or setpriv was
built without libcap-ng and cannot handle --bounding-set=-net_admin, the
wrapper exits 1 before exec'ing the helper, rc is 1, and the test returns
KSFT_PASS with vsock_dev_do_ioctl() never reached.
The &>/dev/null redirection discards the wrapper's own error message, so
that outcome is silent.
check_deps() only verifies that the setpriv binary exists, not that
--bounding-set is supported, so an unsupported setpriv becomes a pass
rather than a skip.
The same conflation applies to the second test below: the pre-flight probe
runs only "unshare -U true", while the command actually executed is
"${unpriv} unshare -Urn ${helper}". A failure of the -r or -n part, or of
setpriv --reuid/--regid/--clear-groups, also exits 1 and is read as the
ioctl's EPERM.
Would it be better for the helper to report a distinct sentinel exit code
(or print a machine-checkable marker) for the ioctl result, so that the
kernel's capable(CAP_NET_ADMIN) check in vsock_dev_do_ioctl() cannot be
weakened without these tests noticing?
+
+test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails() {
+ local helper=/tmp/vsock_assign_g2h_netns
+ local unpriv_uid=65534
+ local unpriv
+ local rc
+
+ unpriv="setpriv --reuid=${unpriv_uid} --regid=${unpriv_uid}"
+ unpriv="${unpriv} --clear-groups"
+
+ if ! vm_ssh "init_ns" -- "${unpriv} unshare -U true"; then
+ log_host "unprivileged user namespaces unavailable, skipping"
+ return "${KSFT_SKIP}"
+ fi
+
+ # The home shared with the guest is root-only, so place the helper where
+ # an unprivileged user can execute it.
+ vm_ssh "init_ns" -- \
+ "cp ./vsock_assign_g2h_netns ${helper} && chmod 755 ${helper}"
+
+ vm_ssh "init_ns" -- "${unpriv} unshare -Urn ${helper}" &>/dev/null
+ rc=$?
+
+ if [[ "${rc}" -ne "${EPERM}" ]]; then
+ log_host "expected EPERM (${EPERM}) for an unprivileged user, got ${rc}"
+ return "${KSFT_FAIL}"
+ fi
[Severity: Medium]
Can uid 65534 open /dev/vsock in this guest at all?
The node is created from a miscdevice with no .mode:
net/vmw_vsock/af_vsock.c
static struct miscdevice vsock_device = {
.name = "vsock",
.fops = &vsock_device_ops,
};
misc_devnode() only overrides the mode when c->mode is non-zero, and
devtmpfs_create_node() then applies mode 0600 with uid/gid
GLOBAL_ROOT_UID/GLOBAL_ROOT_GID. In the bare virtme-ng guest this script
boots there is no udev rule loosening it, so /dev/vsock is 0600 root:root.
With unshare -Urn only kuid 65534 is mapped, so the inode's kuid 0 is
unmapped in the new user namespace and capable_wrt_inode_uidgid() cannot
grant CAP_DAC_OVERRIDE. open("/dev/vsock", O_RDONLY) then fails with
EACCES and the helper does:
tools/testing/selftests/vsock/vsock_assign_g2h_netns.c:main() {
fd = open("/dev/vsock", O_RDONLY);
if (fd < 0) {
fprintf(stderr, "open /dev/vsock: %s\n", strerror(errno));
return -1;
}
which is exit status 255, so this rc check reports "expected EPERM (1) for
an unprivileged user, got 255" and returns KSFT_FAIL even though the
kernel behaved as intended. The stderr message explaining why is dropped
by &>/dev/null.
Would it make sense to test whether the unprivileged uid can open
/dev/vsock first and return KSFT_SKIP if it cannot, so that an
inaccessible device node is distinguished from a refusal by the
CAP_NET_ADMIN check?
+
+ return "${KSFT_PASS}"
+}
+[Severity: Low] Both new tests are listed in USE_SHARED_VM but neither calls vm_reset_g2h, while every other g2h-assign test does so on both the success and failure paths, for example test_ns_guest_assign_g2h_netns_connect_to_host_ok(), ..._init_ns_connect_fails(), ..._host_connect_ok() and ..._reset_on_ns_delete_ok(). If the capable(CAP_NET_ADMIN) gate in vsock_dev_do_ioctl() is ever weakened, the assign succeeds inside the throwaway unshare -n or unshare -Urn namespace and these tests return KSFT_FAIL with the guest's vsock device still owned by a dying namespace. The neighbouring test already documents that the hand-back is not immediate: # The holder is gone, but the namespace itself is dismantled from a # workqueue, so the device does not come back the same instant. Retry # until it does, rather than expecting the first send to succeed. Could the following shared-VM tests then run while the device is still unavailable, turning one real failure into a series of spurious ones? Should these two tests call vm_reset_g2h before returning, like the others?
shared_vm_test() {
local tname-- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902-vsock-guest-ns-v1-0-9995383e9a8b%40meta.com