Ioctl.lean
Declarations on this page (9)
Linux VFS ioctl Syscall
Models Linux AArch64 ioctl (__NR_ioctl = 29, fs/ SYSCALL_DEFINE3(ioctl) and do_vfs_ioctl), covering FIOCLEX, FIONCLEX, FIONREAD on regular files, and -ENOTTY for terminal-only 'T' commands on regular files.
@[expose] public sectionnamespace Arm64def SYS_IOCTL
Linux AArch64 ioctl syscall number (__NR_ioctl = 29, include/, fs/).
def SYS_IOCTL : BitVec 64 := 29#64def FIONREAD_W32
Generic VFS ioctl commands (include/): FIONREAD (0x541B), FIONBIO (0x5421), FIONCLEX (0x5450), FIOCLEX (0x5451), FIOASYNC (0x5452), and FIOQSIZE (0x5460), all handled by do_vfs_ioctl before vfs_ioctl.
def FIONREAD_W32 : BitVec 32 := 0x541B#32def FIONBIO_W32
def FIONBIO_W32 : BitVec 32 := 0x5421#32def FIONCLEX_W32
def FIONCLEX_W32 : BitVec 32 := 0x5450#32def FIOCLEX_W32
def FIOCLEX_W32 : BitVec 32 := 0x5451#32def FIOASYNC_W32
def FIOASYNC_W32 : BitVec 32 := 0x5452#32def FIOQSIZE_W32
def FIOQSIZE_W32 : BitVec 32 := 0x5460#32def isTtyOnlyIoctl
cmd is a terminal (_IOC_TYPE(cmd) == 'T', include/) command that do_vfs_ioctl (fs/) does not handle itself, so on a regular file it falls through file_ioctl -> vfs_ioctl and returns -ENOTTY.
def isTtyOnlyIoctl (cmd : BitVec 32) : Bool :=
((cmd >>> 8) &&& 0xFF#32) == 0x54#32 && (cmd >>> 16) == 0#32 &&
!(cmd == FIONREAD_W32 || cmd == FIONBIO_W32 || cmd == FIONCLEX_W32 ||
cmd == FIOCLEX_W32 || cmd == FIOASYNC_W32 || cmd == FIOQSIZE_W32)def execIoctl
ioctl(fd, cmd, arg) (__NR_ioctl = 29, fs/ SYSCALL_DEFINE3(ioctl) -> do_vfs_ioctl at fs/), reading the low 32 bits of X1 as unsigned int cmd:
-
closed fds
≥ 3and negative fds return-EBADF; -
FIOCLEX/FIONCLEXon open .localFileor .localDirdescriptors set / clearFdEntry.and returncloexec 0(set_close_on_exec); -
FIONREADon .localFilestores the 32-bitinti_size - f_pos(truncated, as byput_user(i_size_read(inode) - filp->f_pos, (int __user *)argp)) atargand returns0, or returns-EFAULTifargis not 4-byte writable; -
terminal-only
cmds (isTtyOnlyIoctl, e.g.TCGETS,TIOCGWINSZ) on .localFilereturn-ENOTTY, assuming the file system'sunlocked_ioctlhandles no'T'commands; -
[INTENTIONAL_SCOPE]: othercmds on .localFile(FIONBIO,FIOASYNC,FIOQSIZE,FIGETBSZ,FS_IOC_*, ...), unmodeled stdio fds, othercmds on .localDir, and other descriptor kinds trap fail-closed.
def execIoctl (s : State) : State :=
let rawFd := s.readReg32 Reg.x0
let cmd := s.readReg32 Reg.x1
let arg := s.readReg64 Reg.x2
if rawFd.slt 0#32 then (s.writeReg64 Reg.x0 (-EBADF)).advancePC
else
let fd := rawFd.zeroExtend 64
match s.fs.fds fd with
| some desc =>
if (desc.kind == .localFile || desc.kind == .localDir) &&
(cmd == FIOCLEX_W32 || cmd == FIONCLEX_W32) then
let fs' := s.fs.setFd fd (some { desc with cloexec := cmd == FIOCLEX_W32 })
((s.setFs fs').writeReg64 Reg.x0 0#64).advancePC
else if desc.kind != .localFile then s.setStatus (.Exception "unmodeled non-file fd")
else if cmd == FIONREAD_W32 then
if !s.mem.canWrite arg 4 then (s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else
let avail : BitVec 32 :=
BitVec.ofNat 32 (s.fs.fdBytes desc).length - BitVec.ofNat 32 desc.offset
((s.setMem (s.mem.writeBytes arg (leBytes32 avail))).writeReg64 Reg.x0 0#64).advancePC
else if isTtyOnlyIoctl cmd then (s.writeReg64 Reg.x0 (-ENOTTY)).advancePC
else s.setStatus (.Exception "unmodeled ioctl cmd")
| none =>
if fd < 3#64 then s.setStatus (.Exception "unmodeled stdio fd")
else (s.writeReg64 Reg.x0 (-EBADF)).advancePCend Arm64