Sharing and Attaching
July 16, 2026 · View on GitHub
A surface becomes useful when more than one capsule can see it: a client draws into it and a
compositor reads it to build the screen. NØNOS shares surfaces by mapping the same physical frames
into a second capsule's address space, so the sharing is zero-copy, and it tracks the sharing with
a refcount and a per-receiver attach map. This page documents share, attach, and release. The code
is under src/kernel_core/surface_registry/share/ and .../release/.
Share
share_surface (share/share_surface.rs:20) is the owner marking a surface available and bumping
its refcount:
share_surface(owner_pid, handle):
decode handle; verify epoch (BadHandle) and owner (NotOwner)
refcount += 1 (checked)
return handle
Only the owner can share, and the epoch is checked, so a capsule cannot share a surface it does not own or a stale handle. The refcount is what keeps the frames alive while any sharer holds them.
Attach
attach_surface (share/attach_surface.rs:25) is the receiving side: it maps the surface's frames
into the receiver's address space and hands back the descriptor and the virtual address:
attach_surface(receiver_pid, handle, out_desc):
if already attached (attach_map lookup): return the recorded VA // idempotent
if receiver is the owner and has a base VA: return owner_base_va // self-attach, no remap
else:
refcount += 1; clone the frame list
reserve a VMA in the receiver of frames.len() pages
map each frame into the receiver's ASID with user read-write perms
record (receiver, handle) -> (VA, len) in the attach map
return the VA
The general case maps the surface's physical frames into a freshly reserved region of the receiver's address space, one page per frame, so both capsules now have the same physical pixels mapped: a write by the client is visible to the compositor with no copy. Two special cases avoid redundant work: a repeat attach returns the VA already recorded for that receiver and handle (idempotent), and a self-attach by the owner returns the VA the owner already registered the surface at rather than creating a second mapping, which would leave a virtual address with no backing VMA and break present. The mapping uses the paging manager per-ASID map, and the receiver's VA comes from its own VMA reservation, so the kernel never guesses a user address.
Release
Surfaces are released two ways (release/): release_surface drops a reference (and frees the
slot and frames when the last reference goes), and release_owned_by_pid is called on process exit
to drop every surface a capsule owned. The attach map is likewise cleaned per handle and per pid
(attach_map/forget*), so a capsule that exits does not leave a surface slot or a stale attachment
behind. Combined with the epoch on the handle, this means a surface's lifetime is bounded by its
references and its owner, and a handle to a released surface fails rather than aliasing a new one.
Security analysis
Sharing is where one capsule's pixels become visible to another, so it is the point in the graphics path with the most opportunity to leak or alias memory across an address-space boundary. Three properties keep it honest, and the same IOMMU boundary as the rest of the DMA-adjacent path applies.
Share and attach are separately gated. Marking a surface shareable goes through
MkSurfaceShare, which the cap table routes to can_surface_create (mk.rs:73); mapping a shared
surface into a receiver goes through MkSurfaceAttach, which routes to can_surface_map
(mk.rs:75), requiring the GraphicsSurfaceMap grant (bit 8192, capabilities/types.rs:69). The
producer of a surface and the consumer that maps it are therefore distinct capabilities: a
compositor that only ever attaches other capsules' surfaces needs the map grant but not the create
grant, and a client that only draws needs create but not map.
Only the owner shares, and the epoch guards the frames. share_surface
(share/share_surface.rs:20) decodes the handle, rejects a non-owner with NotOwner and a stale
epoch with BadHandle, then bumps the refcount under check. So a capsule cannot mark someone else's
surface shareable, and it cannot resurrect a released surface through a stale handle. The refcount is
what keeps the frames alive while any sharer holds them, which means a surface's backing memory is
freed exactly when the last reference drops, not while a compositor is still reading it.
The receiver only ever sees frames it was handed. attach_surface
(share/attach_surface.rs) maps the surface's existing physical frames into the receiver's own ASID
at a VA the receiver reserved through reserve_vma, one page per frame, with user read-write perms
(attach_surface.rs:71). The kernel never invents a receiver address, and it maps only the frame
list recorded in the slot, so a receiver cannot attach its way to memory outside the surface it was
given a handle for. The idempotent and self-attach shortcuts return an already-recorded VA rather
than laying down a second aliasing mapping.
The honest boundary is again the IOMMU. The whole point of the design is that both capsules map the same physical frames so the sharing is zero-copy, which means the CPU-side isolation between the two capsules is deliberately relaxed for exactly those pages. That is safe between two cooperating graphics capsules under paging, but once a display device is programmed with those physical frames, nothing in this path stops the device from reaching other RAM; that bound lives with the DMA broker and its absent IOMMU backend, not with the registry.
Debugging sharing and attaching
Attach is the step that touches two address spaces, so its failures map to the paging-related
RegistryError variants (types.rs:56), surfaced as errnos by map_err (surface_ops.rs:51):
BadHandle -> EINVAL stale epoch: the surface was released and its slot reused
NotOwner -> EPERM share on a surface the caller does not own
NoProc -> ENOTSUP no current process at attach time (attach_surface.rs:72)
MapFailed -> ENOTSUP the cross-ASID mapping itself failed
MapFailed is the interesting one, because it has three distinct origins inside
attach_surface: lookup_asid_for_process returned nothing for the receiver (attach_surface.rs:71),
reserve_vma could not carve a region of frames.len() pages (attach_surface.rs:74), or a
map_page_in_asid call failed while installing a frame (attach_surface.rs:79). All three collapse
to ENOTSUP, so when a compositor attach fails the way to tell them apart is the receiver's state:
a receiver with no ASID is a process-teardown race, a reserve_vma failure is VA exhaustion in the
receiver, and a per-frame map failure points at the frame list or the receiver's page tables. A
BadHandle here almost always means the client released the surface out from under the compositor,
which is exactly the aliasing the epoch is there to catch. Attach also emits [SURFACE] attach enter / attach ok traces on the compositor and shell pids (surface_handlers.rs:110), so you can
see whether attach reached ok.
Source map
src/kernel_core/surface_registry/share/share_surface.rs share (owner, refcount)
src/kernel_core/surface_registry/share/attach_surface.rs attach (cross-ASID frame mapping, MapFailed origins)
src/kernel_core/surface_registry/attach_map/ per-receiver VA record and cleanup
src/kernel_core/surface_registry/release/ release_surface, release_owned_by_pid
src/syscall/contract/cap_table/mk.rs share -> GraphicsSurfaceCreate, attach -> GraphicsSurfaceMap
src/capabilities/types.rs the graphics capability bits
Every reference above is verified against those trees. The descriptor and epoch these operations rest on are on the surfaces page, present and vsync over the shared frames are on the presentation page, and the zero-copy frame mapping uses the paging manager per-ASID map.