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.

> 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

[ ... ]

> @@ -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}"

[ ... ]

> @@ -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

Reply via email to