Select.lean
Standard-Descriptor ppoll Probe
Models the Linux AArch64 ppoll syscall (__NR_ppoll = 73, fs/, include/) restricted to non-blocking descriptor-validity probes on fd ∈ {0, 1, 2} with events = 0, zero timeout, and null signal mask. Any call outside this subset fails closed.
@[expose] public sectionnamespace Arm64def SYS_PPOLL
Linux AArch64 ppoll syscall number (__NR_ppoll = 73, include/, fs/).
def SYS_PPOLL : BitVec 64 := 73#64def POLLNVAL
struct pollfd. bit reporting an invalid / closed file descriptor (POLLNVAL = 0x0020, include/).
def POLLNVAL : BitVec 16 := 0x0020#16def evalPpollEntries
Evaluate rem 8-byte struct pollfd entries ({ int fd; short events; short revents; }, include/) starting at index idx from ufds for non-blocking standard-descriptor validity probes (fd ∈ {0, 1, 2}, events = 0):
-
in Linux
do_pollfd(fs/), ifselect.c:882 fdget(fd)finds no open file (fs.),fds fd = none reventsreceivesPOLLNVAL(0x0020) and the ready count increments by1; -
if
fdis an open.stdioor .localFiledescriptor,vfs_pollmasked withevents | EPOLLERR | EPOLLHUP(events = 0, no error/hangup on regular files or modeled standard streams) yieldsrevents = 0and leaves the ready count unchanged; -
[INTENTIONAL_SCOPE]: any entry outsidefd ∈ {0, 1, 2}, withevents ≠ 0, or backed by.custom _returnsnonesoexecPpollfails closed.
def evalPpollEntries (fs : FsState) (m : Memory) (ufds : BitVec 64) :
Nat → Nat → Nat → Option (Memory × Nat)
| 0, _, count => some (m, count)
| rem + 1, idx, count =>
let entry := ufds + BitVec.ofNat 64 (idx * 8)
let fd := m.read32 entry
let events := m.read16 (entry + 4#64)
if (fd != 0#32 && fd != 1#32 && fd != 2#32) || events != 0#16 then
none
else
match fs.fds (fd.zeroExtend 64) with
| none =>
evalPpollEntries fs (m.write16 (entry + 6#64) POLLNVAL) ufds rem (idx + 1) (count + 1)
| some desc =>
if desc.kind == .stdio || desc.kind == .localFile then
evalPpollEntries fs (m.write16 (entry + 6#64) 0#16) ufds rem (idx + 1) count
else
nonedef execPpoll
ppoll(ufds, nfds, tsp, sigmask, sigsetsize) (__NR_ppoll = 73, SYSCALL_DEFINE5(ppoll) in fs/ and set_user_sigmask in kernel/), restricted to non-blocking descriptor-validity probes on fd ∈ {0, 1, 2} with events = 0, zero timeout, and null signal mask:
-
tsp(X2):-
tsp == 0(infinite timeout) fails closed with .Exception"unmodeled ppoll (null timeout)" ([INTENTIONAL_SCOPE]); -
unreadable 16-byte
struct __kernel_timespecattspreturns-EFAULT(get_timespec64); -
negative
tv_sec, negativetv_nsec, ortv_nsec ≥ 1_000_000_000returns-EINVAL(poll_select_set_timeout/timespec64_validininclude/);linux/ time64.h -
any valid non-zero timeout (
tv_sec ≠ 0ortv_nsec ≠ 0) fails closed with .Exception"unmodeled ppoll (non-zero timeout)";
-
-
sigmask(X3) andsigsetsize(X4):-
when
sigmask == 0,set_user_sigmask(kernel/) returnssignal.c:3158 0immediately without inspectingsigsetsize; -
when
sigmask ≠ 0andsigsetsize ≠ 8, returns-EINVAL; -
when
sigmask ≠ 0andsigsetsize == 8, fails closed with .Exception"unmodeled ppoll (non-null sigmask)";
-
-
ufds(X0) andnfds(W1,unsigned int nfdsindo_sys_poll,fs/):select.c:1022 -
nfds >
FsState.(fixed ceilingmaxOpenFds 1024; configurableRLIMIT_NOFILEis not modeled) returns-EINVAL; -
nfds == 0returns0; -
if
[ufds, ufds + nfds * 8)wraps modulo2 ^ 64or is not readable, returns-EFAULT; -
evaluates all
nfdsentries viaevalPpollEntries(failing closed with .Exception"unmodeled ppoll" if any entry hasfd ∉ {0, 1, 2},events ≠ 0, or a.custombackend); -
if
[ufds, ufds + nfds * 8)is not writable (put_useronrevents), returns-EFAULT; -
otherwise writes
revents(0for open,POLLNVALfor closed) and returns the count of entries with non-zerorevents.
-
-
[OVERPINNED_BEHAVIOR]: Validatestsp(get_timespec64) ->sigsetsize(set_user_sigmask) ->nfds->ufdsreadability/writability, matchingfs/.select.c:1118-1135
def execPpoll (s : State) : State :=
let ufds := s.readReg64 Reg.x0
let nfds := (s.readReg32 Reg.x1).toNat
let tsp := s.readReg64 Reg.x2
let sigmask := s.readReg64 Reg.x3
let sigsetsize := s.readReg64 Reg.x4
if tsp == 0#64 then
s.setStatus (.Exception "unmodeled ppoll (null timeout)")
else if !decide (tsp.toNat + 16 ≤ 2 ^ 64) || !s.mem.allPerm (·.r) tsp 16 then
(s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else if !s.mem.canRead tsp 16 then
s.setStatus (.Exception "ppoll timespec in lazy-free memory")
else
let tvSec := s.mem.read64 tsp
let tvNsec := s.mem.read64 (tsp + 8#64)
if tvSec.slt 0#64 || tvNsec.slt 0#64 || !decide (tvNsec.toNat < 1000000000) then
(s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else if tvSec != 0#64 || tvNsec != 0#64 then
s.setStatus (.Exception "unmodeled ppoll (non-zero timeout)")
else if sigmask != 0#64 && sigsetsize != 8#64 then
(s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else if sigmask != 0#64 then
s.setStatus (.Exception "unmodeled ppoll (non-null sigmask)")
else if nfds > FsState.maxOpenFds then
(s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else if nfds == 0 then
(s.writeReg64 Reg.x0 0#64).advancePC
else if !decide (ufds.toNat + nfds * 8 ≤ 2 ^ 64) || !s.mem.allPerm (·.r) ufds (nfds * 8) then
(s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else if !s.mem.canRead ufds (nfds * 8) then
s.setStatus (.Exception "ppoll ufds in lazy-free memory")
else
match evalPpollEntries s.fs s.mem ufds nfds 0 0 with
| none => s.setStatus (.Exception "unmodeled ppoll")
| some (m', count) =>
if !s.mem.canWrite ufds (nfds * 8) then
(s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else
((s.setMem m').writeReg64 Reg.x0 (BitVec.ofNat 64 count)).advancePCend Arm64