Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 (1) · Low: 1 (1) If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [High] The fix's entire synchronization mechanism is a plain `u64` (`vsock_sock::cached_peer_access`) accessed with… - [Low] Both lockless call sites of vmci_transport_allow_dgram() still carry the comment "NOTE: We access the socket struct without holding… Pre-existing issues: - [Medium] The per-socket decision cache is keyed only on the peer CID and is never invalidated: grep confirms vsock_sock::cached_peer_access… - [Low] The patch keeps a VMCI-transport-only cache inside the generic struct vsock_sock and, in the core header… -- vsock/vmci: make the cached_peer dgram decision race-safe This replaces the two unsynchronized cache fields in struct vsock_sock (cached_peer and cached_peer_allow_dgram) with a single u64 cached_peer_access that packs the peer CID together with a validity and an allow bit. vmci_transport_allow_dgram() now reads and publishes that word with READ_ONCE()/WRITE_ONCE() so that the lockless receive tasklet and the lock_sock() send path are said to be unable to observe a stale allow decision. The v1 posting is referenced from the changelog: https://lore.kernel.org/netdev/[email protected]/ > diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h > index 5549298c1ec6..9d59b8fe2853 100644 > --- a/include/net/af_vsock.h > +++ b/include/net/af_vsock.h > @@ -39,10 +39,13 @@ struct vsock_sock { > * modified outsided of socket create or destruct. > */ > bool trusted; > - bool cached_peer_allow_dgram; /* Dgram communication allowed to > - * cached peer? > - */ > - u32 cached_peer; /* Context ID of last dgram destination check. */ > + /* Cached dgram access decision for the last peer, packed as > + * (cid << 32) | VALID | ALLOW and accessed via READ_ONCE()/ > + * WRITE_ONCE() so the lockless receive tasklet and the > + * lock_sock() send path cannot race to a stale decision. > + * See vmci_transport_allow_dgram(). > + */ > + u64 cached_peer_access; [Severity: Low] This isn't a bug introduced by this patch (the cached_peer and cached_peer_allow_dgram fields already lived here), but since the representation is being rewritten anyway: could the cache move into the VMCI transport's own state (vsk->trans, struct vmci_transport) instead? grep shows cached_peer_access is read and written only from net/vmw_vsock/vmci_transport.c, yet the core header now documents a VMCI-specific bit packing convention and points at vmci_transport_allow_dgram(), which the virtio, hyperv and loopback transports never use. > const struct cred *owner; > /* Rest are SOCK_STREAM only. */ > long connect_timeout; > diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c > index 1f186e8f8364..31728e26a770 100644 > --- a/net/vmw_vsock/vmci_transport.c > +++ b/net/vmw_vsock/vmci_transport.c > @@ -524,23 +524,38 @@ static bool vmci_transport_is_trusted(struct vsock_sock > *vsock, u32 peer_cid) > * only if it is trusted as described in vmci_transport_is_trusted. > */ > > +/* Packing for vsk->cached_peer_access. */ > +#define VMCI_DGRAM_ACCESS_VALID BIT_ULL(0) > +#define VMCI_DGRAM_ACCESS_ALLOW BIT_ULL(1) > +#define VMCI_DGRAM_ACCESS_CID_SHIFT 32 > + > static bool vmci_transport_allow_dgram(struct vsock_sock *vsock, u32 > peer_cid) > { > + u64 access; > + > if (VMADDR_CID_HYPERVISOR == peer_cid) > return true; > > - if (vsock->cached_peer != peer_cid) { > - vsock->cached_peer = peer_cid; > - if (!vmci_transport_is_trusted(vsock, peer_cid) && > - (vmci_context_get_priv_flags(peer_cid) & > - VMCI_PRIVILEGE_FLAG_RESTRICTED)) { > - vsock->cached_peer_allow_dgram = false; > - } else { > - vsock->cached_peer_allow_dgram = true; > - } > - } > - > - return vsock->cached_peer_allow_dgram; > + /* Cache the trusted/restricted decision for the last peer to avoid the > + * O(N) vmci_ctx_get() lookup on every datagram. Read/update it through > + * a single word so a race between the lockless receive tasklet and the > + * lock_sock() send path only forces a recompute -- it can never return > a > + * stale allow for a restricted peer. > + */ > + access = READ_ONCE(vsock->cached_peer_access); > + if ((access & VMCI_DGRAM_ACCESS_VALID) && > + (u32)(access >> VMCI_DGRAM_ACCESS_CID_SHIFT) == peer_cid) > + return !!(access & VMCI_DGRAM_ACCESS_ALLOW); [Severity: High] Does the "single word" property actually hold on 32-bit builds? The CID lives in bits 32-63 and VALID/ALLOW in bits 0-1, so the tag and the decision are in two different 32-bit access units, and a plain u64 READ_ONCE()/WRITE_ONCE() is not single-copy atomic there. include/asm-generic/rwonce.h is explicit about it: /* * Yes, this permits 64-bit accesses on 32-bit architectures. These will * actually be atomic in some cases (namely Armv7 + LPAE), but for others we * rely on the access being split into 2x32-bit accesses for a 32-bit quantity * (e.g. a virtual address) and a strong prevailing wind. */ CONFIG_VMWARE_VMCI builds on 32-bit x86 as well: drivers/misc/vmw_vmci/Kconfig: depends on (X86 || ARM64) && !CPU_BIG_ENDIAN && PCI So with the cache holding (A << 32) | VALID | ALLOW, and vmci_transport_dgram_enqueue() -> vmci_transport_allow_dgram() publishing (B << 32) | VALID for a restricted peer B: CPU0 (sendmsg, lock_sock held, i386: two 32-bit stores) store high word: CID = B <-- softirq / other CPU runs here store low word: VALID (ALLOW cleared) CPU1 (vmci_transport_recv_dgram_cb(), receive tasklet, no socket lock) access = READ_ONCE(vsock->cached_peer_access); sees high word B, low word still VALID | ALLOW returns true for the restricted peer B The reader side can tear the same way, since gcc on i386 loads the low word first. Since lock_sock() does not exclude softirqs, this also happens on a single CPU. Would an atomic64_t / cmpxchg64 based field, a seqcount, or an encoding that fits in one native word (for example a separate validity/allow flag plus a CID compared after the flags load, or a 30-bit CID plus two flag bits in an unsigned long) close this for CONFIG_X86_32? As written, the unconditional claims in the changelog ("pack the peer CID and the decision into a single word accessed with READ_ONCE()/WRITE_ONCE(). A race then only forces a recompute and can never return a stale allow"), in the new function comment, and in the new struct vsock_sock comment hold only on 64-bit. Could those be either fixed or scoped? [Severity: Medium] This is a pre-existing issue and not introduced by this patch, since the old "if (vsock->cached_peer != peer_cid)" logic had the same lifetime semantics, but the cache-hit path above is keyed on the peer CID alone and is never invalidated. Can it outlive the context it describes? vmci_ctx_create() only avoids CIDs of contexts that are currently live: drivers/misc/vmw_vmci/vmci_context.c:vmci_ctx_create() { while (vmci_ctx_exists(cid)) { /* We reserve the lowest 16 ids for fixed contexts. */ cid = max(cid, VMCI_RESERVED_CID_LIMIT - 1) + 1; ... } and IOCTL_VMCI_INIT_CONTEXT lets the caller pick both the CID and the privilege flags: drivers/misc/vmw_vmci/vmci_host.c:vmci_host_do_init_context() { vmci_host_dev->context = vmci_ctx_create(init_block.cid, init_block.flags, 0, ... } So an ALLOW cached for CID X can survive destruction of that context, and a later context reusing CID X with VMCI_PRIVILEGE_FLAG_RESTRICTED (or a different owning uid) keeps being allowed without vmci_context_get_priv_flags() being consulted again. The inverse direction exists too: vmci_context_get_priv_flags() returns VMCI_NO_PRIVILEGE_FLAGS while !vmci_host_code_active(), so an ALLOW cached in that window survives host personality activation. Would a generation counter bumped on VMCI context create/destroy, folded into the cached word, address this? > + > + access = VMCI_DGRAM_ACCESS_VALID | > + ((u64)peer_cid << VMCI_DGRAM_ACCESS_CID_SHIFT); > + if (vmci_transport_is_trusted(vsock, peer_cid) || > + !(vmci_context_get_priv_flags(peer_cid) & > + VMCI_PRIVILEGE_FLAG_RESTRICTED)) > + access |= VMCI_DGRAM_ACCESS_ALLOW; > + > + WRITE_ONCE(vsock->cached_peer_access, access); [Severity: Low] Should the two call-site comments be updated in the same patch? Both lockless callers still say the opposite of the new struct vsock_sock comment: net/vmw_vsock/vmci_transport.c:vmci_transport_recv_dgram_cb() { ... * NOTE: We access the socket struct without holding the lock here. * This is ok because the field we are interested is never modified * outside of the create and destruct socket functions. */ vsk = vsock_sk(sk); if (!vmci_transport_allow_dgram(vsk, dg->src.context)) ... } The same text sits above the call in vmci_transport_recv_stream_cb(), and both copies are still there at the end of the series (45f790ccc007). The helper now does WRITE_ONCE(vsock->cached_peer_access, access) from exactly that tasklet context, and the changelog already calls the comment out ("The in-code comment claiming the fields are never modified outside create/destruct is contradicted by the send path") without touching it. > + return !!(access & VMCI_DGRAM_ACCESS_ALLOW); > } > > static int -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919123208.29032-1-bartlomiej.dmitruk%40isec.pl

