Repository navigation
portal_vfs: host reads of penguin-modelled pseudofiles always fail -EINVAL on 5.6+, and it cannot be fixed from the model layer #103
Description
Host reads of penguin-modelled pseudofiles always fail with -EINVAL on 5.6+, and cannot be fixed from the model layer. Measured live on all 10 6.13 CI combos; the same paths read fine on 4.10.
Mechanism
__kernel_read() refuses a whole class of file outright:
if (unlikely(!file->f_op->read_iter || file->f_op->read))
return warn_unsupported(file, "read"); /* -EINVAL */That is a property of the file's f_op, not of statefulness — and for a procfs file the module does not choose the f_op. PROCFS does, at registration time (fs/proc/inode.c, 5.6+):
proc_ops->proc_read_iter != NULL→ inode getsproc_iter_file_ops(.read_iter, no.read) → passes the guard- otherwise →
proc_reg_file_ops(.read, no.read_iter) → refused
No read model in penguin's tree sets read_iter (pyplugins/hyperfile/models/read.py — 17 classes, 8 read implementations, zero read_iter), so every modelled pseudofile lands in the refused class. igloo_convert_ops_to_proc_ops (src/portal/portal_procfs.c:289) faithfully copies ops->read_iter, which is NULL.
The rule is written up with citations in portal_vfs.c:92-108 and asserted in penguin's tests/unit/test_pseudofile_host_readability.py.
Why not fix it at the model layer
The obvious fix — have the read models implement read_iter — is the wrong one:
- A Python
read_itermust decodestruct iov_iter, a tagged union overITER_UBUF/IOVEC/KVEC/BVEC/XARRAY, and then advance it in place. That is kernel-internal iterator machinery reimplemented in Python, across 13 architectures and two kernel eras, to reach a buffer the kernel will hand us for free. read_iterisfrom_plugin-only in the wiring table (pyplugins/hyperfile/pseudofiles.py:184,EXTRA_OPS), so exposing it to the built-in models also means changing the config schema (src/penguin/penguin_config/structure.py:1138,1253,1274).- A model would have to keep
readas well — on 4.10 every regular procfs file getsproc_reg_file_opswhatever the module supplied, andproc_reg_readforwards only->read, soread_iteralone is-EIOthere, to the guest as much as to us.
Proposed fix (host-side, in this repo)
vfs_read_chunk() (src/portal/portal_vfs.c:284) already has exactly this shape — one special case that sidesteps kernel_read for a class of file it can prove something about:
if (!f->f_op->read_iter && f->f_op->read == seq_read) { ... seq_read_iter(&kio, &it); }Add a sibling arm for our own modelled files: identify the file as one of ours, then call the model's read directly with a kernel buffer, skipping proc_reg_read and the guard entirely.
The primitive already exists and is already in production: igloo_fetch_mmap_page() (src/portal/portal_procfs.c:61) trampolines a model's ->read into a kernel bounce buffer. igloo_proxy_mmap has used it on both procfs (portal_procfs.c:131) and devfs (portal_devfs.c:280) since forever.
Two details:
- Identification must be membership-checked, not inferred.
pde_data(file_inode(f))returns ourstruct portal_procfs_entryfor our files, but for someone else's procfs file it returns their data pointer, which we would be misreading.procfs_entry_listalready exists (portal_procfs.c:29, entries added at:905) — check the pointer against it and the identification is airtight. Notef_op->owner == THIS_MODULEdoes not work: on 6.13 thef_opis the kernel'sproc_reg_file_ops, not ours, which is precisely the case that matters. struct portal_procfs_entrydoes not store the fops (src/portal/portal_types.h:219-229); the registration path passes&req->fops, which is transient. The devfs entry does store them and uses them exactly this way (portal_devfs.c:520). So this needs an additivestruct file_operations fopsmember on the procfs entry, copied at registration. No wire-format change.
Alternative considered and rejected
We could instead synthesize proc_read_iter for every model at registration, letting procfs pick proc_iter_file_ops. That is nearly one line and it would work — but it flips the guest read path for every modelled procfs file on 5.6+ onto a bounce buffer, i.e. the same 13-architecture exposure, in order to fix a host-side problem. The vfs_read_chunk arm leaves the guest datapath untouched.
Scope and verification
~60 lines of C, one additive struct member, no Python, no schema change, no wire-format change.
pyplugins/testing/vfs_read_verify.py already reports a per-file verdict on every CI combo, so the fix is directly observable: the modelled-pseudofile rows flip from refused to readable on all 10 6.13 combos, with the 7 4.10 combos unchanged.
Depends on #102 (which touches portal_vfs.c).
Activity