fix: default allow-other to #f, truncate leak, rename checks, bounds validation, zero passphrases
ober
a7d6c064bddc9c5e41ff238190a72ca9567452e8
--- a/src/jerboa-fuse.ss +++ b/src/jerboa-fuse.ss @@ -154,14 +154,14 @@ (def (create-session ops mountpoint options) (let* ([fsname (get-option options 'fsname "chez-fuse")] [debug? (get-option options 'debug #f)] - [allow-other? (get-option options 'allow-other #t)] + [allow-other? (get-option options 'allow-other #f)] [fd (fuse-open-device)] [mtx (make-mutex)] [ac (get-option options 'access-controller #f)] [session (make-fuse-session fd mountpoint #f #f FUSE-KERNEL-VERSION FUSE-KERNEL-MINOR-VERSION - 131072 131072 ;; max-write, max-readahead + 131072 131072 ;; max_write, max_readahead ops mtx ;; dispatch mutex #f ;; thread handle @@ -169,7 +169,12 @@ ac)] ;; access controller [uid (fuse-current-uid)] [gid (fuse-current-gid)]) - (fuse-mount! fd mountpoint fsname uid gid allow-other?) + (guard (exn + [else + (fuse-close-device fd) + (fuse-session-fd-set! session -1) + (raise exn)]) + (fuse-mount! fd mountpoint fsname uid gid allow-other?)) (fuse-session-mounted?-set! session #t) (fuse-session-running?-set! session #t) (when debug? @@ -298,6 +303,9 @@ ;; Opcode dispatch ;; ====================================================================== + (def (handler-error-code result) + (and (integer? result) (negative? result) (- result))) + (def (dispatch-opcode session ops opcode unique nodeid ctx buf off limit) (cond @@ -415,11 +423,14 @@ (let ([handler (get-op ops 'mkdir)]) (if handler (let-values ([(mode umask) (decode-mkdir-in buf off)]) - (let* ([name (extract-name buf (+ off 8) limit)] - [result (handler nodeid name mode ctx)]) - (if result - (encode-entry-out unique result) - (encode-error unique EIO)))) + (let* ([name (extract-name buf (+ off 8) limit)] + [result (handler nodeid name mode ctx)]) + (cond + [(handler-error-code result) + => (lambda (e) (encode-error unique e))] + [result + (encode-entry-out unique result)] + [else (encode-error unique EIO)]))) (encode-error unique ENOSYS)))] ;; ---- FUSE_UNLINK ---- @@ -451,10 +462,13 @@ (let ([newdir (decode-rename-in buf off)]) (let-values ([(oldname newname) (extract-two-names buf (+ off 8) limit)]) - (let ([result (handler nodeid oldname newdir newname ctx)]) - (if result - (encode-out-header unique 0 0) - (encode-error unique EIO))))) + (let ([result (handler nodeid oldname newdir newname ctx)]) + (cond + [(handler-error-code result) + => (lambda (e) (encode-error unique e))] + [result + (encode-out-header unique 0 0)] + [else (encode-error unique EIO)])))) (encode-error unique ENOSYS)))] ;; ---- FUSE_LINK ---- @@ -507,13 +521,15 @@ (if handler (let-values ([(fh offset size write-flags) (decode-write-in buf off)]) - (let* ([data-off (+ off 40)] - [data (make-bytevector size)]) - (bytevector-copy! buf data-off data 0 size) - (let ([written (handler nodeid fh data offset ctx)]) - (if written - (encode-write-out unique written) - (encode-error unique EIO))))) + (let* ([data-off (+ off 40)]) + (if (> (+ data-off size) limit) + (encode-error unique EINVAL) + (let ([data (make-bytevector size)]) + (bytevector-copy! buf data-off data 0 size) + (let ([written (handler nodeid fh data offset ctx)]) + (if written + (encode-write-out unique written) + (encode-error unique EIO))))))) (encode-error unique ENOSYS)))] ;; ---- FUSE_STATFS ---- @@ -602,30 +618,33 @@ (if handler (let-values ([(flags mode umask open-flags) (decode-create-in buf off)]) - (let* ([name (extract-name buf (+ off 16) limit)] - [result (handler nodeid name mode flags ctx)]) - (if result - (let* ([entry (car result)] - [fh-part (cdr result)] - [fh (if (pair? fh-part) (car fh-part) fh-part)] - [oflags (if (pair? fh-part) (cdr fh-part) 0)] - [total (+ FUSE-OUT-HEADER-SIZE - FUSE-ENTRY-OUT-SIZE - FUSE-OPEN-OUT-SIZE)] - [resp (make-bytevector total 0)] - [entry-bv (encode-entry-out unique entry)]) - (bytevector-u32-native-set! resp 0 total) - (bytevector-s32-native-set! resp 4 0) - (bytevector-u64-native-set! resp 8 unique) - (bytevector-copy! entry-bv FUSE-OUT-HEADER-SIZE - resp FUSE-OUT-HEADER-SIZE - FUSE-ENTRY-OUT-SIZE) - (bytevector-u64-native-set! resp - (+ FUSE-OUT-HEADER-SIZE FUSE-ENTRY-OUT-SIZE) fh) - (bytevector-u32-native-set! resp - (+ FUSE-OUT-HEADER-SIZE FUSE-ENTRY-OUT-SIZE 8) oflags) - resp) - (encode-error unique EIO)))) + (let* ([name (extract-name buf (+ off 16) limit)] + [result (handler nodeid name mode flags ctx)]) + (cond + [(handler-error-code result) + => (lambda (e) (encode-error unique e))] + [result + (let* ([entry (car result)] + [fh-part (cdr result)] + [fh (if (pair? fh-part) (car fh-part) fh-part)] + [oflags (if (pair? fh-part) (cdr fh-part) 0)] + [total (+ FUSE-OUT-HEADER-SIZE + FUSE-ENTRY-OUT-SIZE + FUSE-OPEN-OUT-SIZE)] + [resp (make-bytevector total 0)] + [entry-bv (encode-entry-out unique entry)]) + (bytevector-u32-native-set! resp 0 total) + (bytevector-s32-native-set! resp 4 0) + (bytevector-u64-native-set! resp 8 unique) + (bytevector-copy! entry-bv FUSE-OUT-HEADER-SIZE + resp FUSE-OUT-HEADER-SIZE + FUSE-ENTRY-OUT-SIZE) + (bytevector-u64-native-set! resp + (+ FUSE-OUT-HEADER-SIZE FUSE-ENTRY-OUT-SIZE) fh) + (bytevector-u32-native-set! resp + (+ FUSE-OUT-HEADER-SIZE FUSE-ENTRY-OUT-SIZE 8) oflags) + resp)] + [else (encode-error unique EIO)]))) (encode-error unique ENOSYS)))] ;; ---- FUSE_INTERRUPT ---- --- a/src/jerboa-fuse/access.ss +++ b/src/jerboa-fuse/access.ss @@ -25,12 +25,14 @@ (def c-getpid #f) (def c-getppid-of #f) + (def c-get-start-time #f) (def *bindings-ready?* #f) (def (ensure-access-bindings!) (when (and (ensure-mount-lib!) (not *bindings-ready?*)) (set! c-getpid (c-lambda () int "jerboa_fuse_getpid")) (set! c-getppid-of (c-lambda (int) int "jerboa_fuse_getppid_of")) + (set! c-get-start-time (c-lambda (int) integer-64 "jerboa_fuse_get_start_time")) (set! *bindings-ready?* #t)) (unless *bindings-ready?* (error 'jerboa-fuse/access "unable to load native process helpers"))) @@ -58,8 +60,9 @@ ;; ---- Access controller ---- - ;; Cache entry: (pid . expiry-time) + ;; Cache entry: pid → (start-time . expiry-time) ;; Trusted PIDs are cached for a short TTL to avoid repeated sysctl calls. + ;; The start-time guards against PID reuse within the TTL window. ;; Denied PIDs are NOT cached (process might become a child later, though ;; unlikely — and we want to re-check in case of PID reuse). (def CACHE-TTL 5) ;; seconds @@ -101,18 +104,23 @@ [else (let* ([cache (access-controller-state-cache ac)] [now (time-second (current-time))] - [expiry (eq-hashtable-ref cache pid #f)]) + [entry (eq-hashtable-ref cache pid #f)]) (cond - ;; Cache hit, not expired - [(and expiry (> expiry now)) #t] - ;; Cache miss or expired — do the walk + ;; Cache hit, not expired, and start time matches (no PID reuse) + [(and entry + (> (cdr entry) now) + (let ([st (c-get-start-time pid)]) + (and (>= st 0) (= st (car entry))))) + #t] + ;; Cache miss, expired, or PID reused — do the walk [else (let ([trusted? (pid-is-descendant? pid (access-controller-state-owner-pid ac))]) (when trusted? - ;; Cache positive result - (eq-hashtable-set! cache pid (+ now CACHE-TTL))) + (let ([st (c-get-start-time pid)]) + (eq-hashtable-set! cache pid + (cons st (+ now CACHE-TTL))))) trusted?)]))]))) ;; ---- Stealth deny responses ---- --- a/src/jerboa-fuse/memfs.ss +++ b/src/jerboa-fuse/memfs.ss @@ -436,14 +436,50 @@ (let* ([old-children (memfs-node-data old-parent)] [ino (hashtable-ref old-children old-name #f)]) (if ino - (let ([new-children (memfs-node-data new-parent)]) - ;; Remove old entry that might exist at destination - (let ([existing (hashtable-ref new-children new-name #f)]) - (when existing - (hashtable-delete! (memfs-state-inodes fs) existing))) - (hashtable-delete! old-children old-name) - (hashtable-set! new-children new-name ino) - #t) + (let ([src-node (get-node fs ino)] + [new-children (memfs-node-data new-parent)]) + (if (not src-node) + #f + (let ([existing (hashtable-ref new-children new-name #f)]) + (if existing + (let ([dst-node (get-node fs existing)]) + (if (not dst-node) + #f + (cond + [(and (eq? (memfs-node-type src-node) 'dir) + (not (eq? (memfs-node-type dst-node) 'dir))) + #f] + [(and (not (eq? (memfs-node-type src-node) 'dir)) + (eq? (memfs-node-type dst-node) 'dir)) + #f] + [(and (eq? (memfs-node-type dst-node) 'dir) + (> (hashtable-size (memfs-node-data dst-node)) 0)) + #f] + [else + (let ([nl (- (memfs-node-nlink dst-node) 1)]) + (memfs-node-nlink-set! dst-node nl) + (when (<= nl 0) + (hashtable-delete! (memfs-state-inodes fs) existing))) + (when (eq? (memfs-node-type dst-node) 'dir) + (memfs-node-nlink-set! new-parent + (max 2 (- (memfs-node-nlink new-parent) 1)))) + (hashtable-delete! old-children old-name) + (hashtable-set! new-children new-name ino) + (when (eq? (memfs-node-type src-node) 'dir) + (memfs-node-nlink-set! old-parent + (max 2 (- (memfs-node-nlink old-parent) 1))) + (memfs-node-nlink-set! new-parent + (+ (memfs-node-nlink new-parent) 1))) + #t]))) + (begin + (hashtable-delete! old-children old-name) + (hashtable-set! new-children new-name ino) + (when (eq? (memfs-node-type src-node) 'dir) + (memfs-node-nlink-set! old-parent + (max 2 (- (memfs-node-nlink old-parent) 1))) + (memfs-node-nlink-set! new-parent + (+ (memfs-node-nlink new-parent) 1))) + #t))))) #f)) #f)))) --- a/src/jerboa-fuse/vault.ss +++ b/src/jerboa-fuse/vault.ss @@ -331,7 +331,8 @@ [(< new-size old-size) ;; Shrink: zero out tail of last block; free excess blocks (let* ([last-lidx (if (zero? new-size) -1 (quotient (- new-size 1) BLOCK-PAYLOAD))] - [last-boff (if (zero? new-size) 0 (remainder new-size BLOCK-PAYLOAD))]) + [last-boff (if (zero? new-size) 0 (remainder new-size BLOCK-PAYLOAD))] + [max-lidx (if (zero? old-size) -1 (quotient (- old-size 1) BLOCK-PAYLOAD))]) ;; Zero tail of last partial block (when (and (>= last-lidx 0) (> last-boff 0)) (let ([phys (resolve-data-block vault inode last-lidx)]) @@ -343,22 +344,23 @@ (bytevector-u8-set! pay i 0) (loop (+ i 1)))) (blockstore-write-block! (vault-state-bs vault) phys pay)))))) - ;; Free blocks beyond last-lidx + ;; Free blocks beyond last-lidx, iterating all indices (sparse-aware) (let loop ([i (+ last-lidx 1)]) - (let ([phys (resolve-data-block vault inode i)]) - (when phys - (bitmap-free! vault phys) - (if (< i DIRECT-BLOCKS) - (vector-set! (vault-inode-direct inode) i VAULT-BLOCK-INVALID) - (let ([ind-blk (vault-inode-indirect inode)]) - (when (not (= ind-blk VAULT-BLOCK-INVALID)) - (let ([ind-pay (blockstore-read-block (vault-state-bs vault) ind-blk)]) - (when ind-pay - (let ([ptr-idx (- i DIRECT-BLOCKS)]) - (bv-set-u64le! ind-pay (* ptr-idx 8) VAULT-BLOCK-INVALID) - (blockstore-write-block! (vault-state-bs vault) ind-blk ind-pay))))))) - (loop (+ i 1)))))) - (vault-inode-size-set! inode new-size)] + (when (<= i max-lidx) + (let ([phys (resolve-data-block vault inode i)]) + (when phys + (bitmap-free! vault phys) + (if (< i DIRECT-BLOCKS) + (vector-set! (vault-inode-direct inode) i VAULT-BLOCK-INVALID) + (let ([ind-blk (vault-inode-indirect inode)]) + (when (not (= ind-blk VAULT-BLOCK-INVALID)) + (let ([ind-pay (blockstore-read-block (vault-state-bs vault) ind-blk)]) + (when ind-pay + (let ([ptr-idx (- i DIRECT-BLOCKS)]) + (bv-set-u64le! ind-pay (* ptr-idx 8) VAULT-BLOCK-INVALID) + (blockstore-write-block! (vault-state-bs vault) ind-blk ind-pay))))))))) + (loop (+ i 1)))) + (vault-inode-size-set! inode new-size))] [else ;; Extend: just update size (data blocks will read as zeros) (vault-inode-size-set! inode new-size)]) @@ -496,7 +498,8 @@ [salt (vault-rand-bytes 32)] [master-key (vault-rand-bytes VAULT-KEY-LEN)] ;; Derive passphrase key - [pk (vault-pbkdf2 pass-bv salt KDF-ITERATIONS VAULT-KEY-LEN)] + [pk (vault-pbkdf2 pass-bv salt KDF-ITERATIONS VAULT-KEY-LEN)] + [_ (bytevector-fill! pass-bv 0)] ;; Encrypt master key [mk-enc (vault-encrypt-small pk master-key)] ;; Encrypt superblock block number (always 0) @@ -566,6 +569,7 @@ (decode-vault-header hdr-bv)]) ;; Derive passphrase key (let ([pk (vault-pbkdf2 pass-bv salt kdf-iter VAULT-KEY-LEN)]) + (bytevector-fill! pass-bv 0) ;; Decrypt master key (let ([master-key (vault-decrypt-small pk mk-enc)]) (unless master-key @@ -653,7 +657,8 @@ (let* ([pass-bv (if (string? passphrase) (string->utf8 passphrase) passphrase)]) (let-values ([(_magic _ver _blksz _total salt kdf-iter mk-enc sb-enc) (decode-vault-header hdr-bv)]) - (let ([pk (vault-pbkdf2 pass-bv salt kdf-iter VAULT-KEY-LEN)]) + (let ([pk (vault-pbkdf2 pass-bv salt kdf-iter VAULT-KEY-LEN)]) + (bytevector-fill! pass-bv 0) (let ([master-key (vault-decrypt-small pk mk-enc)]) (bytevector-fill! pk 0) (unless master-key @@ -763,23 +768,27 @@ (let ([parent (read-inode vault parent-ino)]) (and parent (= (vault-inode-type parent) INODE-TYPE-DIR) - (let ([new-blk (bitmap-alloc! vault)]) - (and new-blk - (let* ([now (time-second (current-time))] - [uid (fuse-context-uid ctx)] - [gid (fuse-context-gid ctx)] - [inode (make-vault-inode - new-blk INODE-TYPE-FILE - (bitwise-ior S-IFREG (bitwise-and mode #o7777)) - uid gid 0 now now now 1 - (string->utf8 name) - (make-vector DIRECT-BLOCKS VAULT-BLOCK-INVALID) - VAULT-BLOCK-INVALID)]) - (write-inode! vault inode) - (dir-add! vault parent-ino parent new-blk name) - (write-inode! vault parent) - (let ([fh (alloc-fh! vault new-blk)]) - (cons (inode->fuse-entry inode) fh)))))))))) + (if (> (bytevector-length (string->utf8 name)) VAULT-MAX-FILENAME) + (- ENAMETOOLONG) + (if (dir-lookup vault parent name) + (- EEXIST) + (let ([new-blk (bitmap-alloc! vault)]) + (and new-blk + (let* ([now (time-second (current-time))] + [uid (fuse-context-uid ctx)] + [gid (fuse-context-gid ctx)] + [inode (make-vault-inode + new-blk INODE-TYPE-FILE + (bitwise-ior S-IFREG (bitwise-and mode #o7777)) + uid gid 0 now now now 1 + (string->utf8 name) + (make-vector DIRECT-BLOCKS VAULT-BLOCK-INVALID) + VAULT-BLOCK-INVALID)]) + (write-inode! vault inode) + (dir-add! vault parent-ino parent new-blk name) + (write-inode! vault parent) + (let ([fh (alloc-fh! vault new-blk)]) + (cons (inode->fuse-entry inode) fh)))))))))))) (def (make-vault-mkdir vault) (lambda (parent-ino name mode ctx) @@ -787,23 +796,27 @@ (let ([parent (read-inode vault parent-ino)]) (and parent (= (vault-inode-type parent) INODE-TYPE-DIR) - (let ([new-blk (bitmap-alloc! vault)]) - (and new-blk - (let* ([now (time-second (current-time))] - [uid (fuse-context-uid ctx)] - [gid (fuse-context-gid ctx)] - [inode (make-vault-inode - new-blk INODE-TYPE-DIR - (bitwise-ior S-IFDIR (bitwise-and mode #o7777)) - uid gid 0 now now now 2 - (string->utf8 name) - (make-vector DIRECT-BLOCKS VAULT-BLOCK-INVALID) - VAULT-BLOCK-INVALID)]) - (write-inode! vault inode) - (dir-add! vault parent-ino parent new-blk name) - (vault-inode-nlink-set! parent (+ (vault-inode-nlink parent) 1)) - (write-inode! vault parent) - (inode->fuse-entry inode))))))))) + (if (> (bytevector-length (string->utf8 name)) VAULT-MAX-FILENAME) + (- ENAMETOOLONG) + (if (dir-lookup vault parent name) + (- EEXIST) + (let ([new-blk (bitmap-alloc! vault)]) + (and new-blk + (let* ([now (time-second (current-time))] + [uid (fuse-context-uid ctx)] + [gid (fuse-context-gid ctx)] + [inode (make-vault-inode + new-blk INODE-TYPE-DIR + (bitwise-ior S-IFDIR (bitwise-and mode #o7777)) + uid gid 0 now now now 2 + (string->utf8 name) + (make-vector DIRECT-BLOCKS VAULT-BLOCK-INVALID) + VAULT-BLOCK-INVALID)]) + (write-inode! vault inode) + (dir-add! vault parent-ino parent new-blk name) + (vault-inode-nlink-set! parent (+ (vault-inode-nlink parent) 1)) + (write-inode! vault parent) + (inode->fuse-entry inode))))))))))) (def (make-vault-unlink vault) (lambda (parent-ino name ctx) @@ -858,20 +871,54 @@ (= (vault-inode-type new-parent) INODE-TYPE-DIR) (let ([child-blk (dir-lookup vault old-parent old-name)]) (and child-blk - (begin - ;; Remove from old parent - (dir-remove! vault old-parent old-name) - ;; If destination exists, remove it - (let ([existing (dir-lookup vault new-parent new-name)]) - (when existing - (let ([ex-inode (read-inode vault existing)]) - (when ex-inode - (free-inode-blocks! vault ex-inode) - (flush-bitmap! vault))))) - ;; Add to new parent - (dir-add! vault new-parent-ino new-parent child-blk new-name) - (write-inode! vault new-parent) - #t)))))))) + (let ([child-inode (read-inode vault child-blk)]) + (and child-inode + (let ([existing (dir-lookup vault new-parent new-name)]) + (if existing + (let ([ex-inode (read-inode vault existing)]) + (and ex-inode + (cond + [(and (= (vault-inode-type child-inode) INODE-TYPE-DIR) + (not (= (vault-inode-type ex-inode) INODE-TYPE-DIR))) + #f] + [(and (not (= (vault-inode-type child-inode) INODE-TYPE-DIR)) + (= (vault-inode-type ex-inode) INODE-TYPE-DIR)) + #f] + [(and (= (vault-inode-type ex-inode) INODE-TYPE-DIR) + (not (dir-empty? vault ex-inode))) + #f] + [else + (if (> (vault-inode-nlink ex-inode) 1) + (vault-inode-nlink-set! ex-inode + (- (vault-inode-nlink ex-inode) 1)) + (begin + (free-inode-blocks! vault ex-inode) + (flush-bitmap! vault))) + (when (= (vault-inode-type ex-inode) INODE-TYPE-DIR) + (vault-inode-nlink-set! new-parent + (max 2 (- (vault-inode-nlink new-parent) 1)))) + (dir-remove! vault new-parent new-name) + (dir-remove! vault old-parent old-name) + (dir-add! vault new-parent-ino new-parent child-blk new-name) + (when (= (vault-inode-type child-inode) INODE-TYPE-DIR) + (vault-inode-nlink-set! old-parent + (max 2 (- (vault-inode-nlink old-parent) 1))) + (vault-inode-nlink-set! new-parent + (+ (vault-inode-nlink new-parent) 1))) + (write-inode! vault old-parent) + (write-inode! vault new-parent) + #t]))) + (begin + (dir-remove! vault old-parent old-name) + (dir-add! vault new-parent-ino new-parent child-blk new-name) + (when (= (vault-inode-type child-inode) INODE-TYPE-DIR) + (vault-inode-nlink-set! old-parent + (max 2 (- (vault-inode-nlink old-parent) 1))) + (vault-inode-nlink-set! new-parent + (+ (vault-inode-nlink new-parent) 1))) + (write-inode! vault old-parent) + (write-inode! vault new-parent) + #t)))))))))))) (def (make-vault-setattr vault) (lambda (ino valid fh size atime mtime ctime atimensec mtimensec ctimensec mode uid gid ctx) --- a/src/jerboa-fuse/vault/blockstore.ss +++ b/src/jerboa-fuse/vault/blockstore.ss @@ -246,24 +246,26 @@ ;; of the crypto operation. The temporary bytevector is zeroed afterward. (def (blockstore-read-block bs block-num) - ;; Returns decrypted BLOCK-PAYLOAD-byte bv, or #f on auth failure / I/O error. + ;; Returns decrypted BLOCK-PAYLOAD-byte bv, or #f on I/O error / bad block. + ;; Raises an error if no master key is set (auth failure / locked vault). (with-mutex (blockstore-state-mutex bs) (ensure-block-num 'blockstore-read-block bs block-num) (let ([sk (blockstore-state-master-key bs)]) - (and sk (secure-key? sk) (secure-key-live? sk) - (let* ([raw-bv (make-bytevector BLOCK-SIZE 0)] - [offset (block-offset block-num)]) - (raw-read! bs offset raw-bv) - (call-with-secure-key sk - (lambda (mk-bv) - (let ([bk #f]) - (dynamic-wind - (lambda () (void)) - (lambda () - (set! bk (vault-block-key mk-bv block-num)) - (vault-decrypt-block bk raw-bv)) - (lambda () - (when bk (bytevector-fill! bk 0)))))))))))) + (unless (and sk (secure-key? sk) (secure-key-live? sk)) + (error 'blockstore-read-block "no master key set (vault locked or auth failure)")) + (let* ([raw-bv (make-bytevector BLOCK-SIZE 0)] + [offset (block-offset block-num)]) + (raw-read! bs offset raw-bv) + (call-with-secure-key sk + (lambda (mk-bv) + (let ([bk #f]) + (dynamic-wind + (lambda () (void)) + (lambda () + (set! bk (vault-block-key mk-bv block-num)) + (vault-decrypt-block bk raw-bv)) + (lambda () + (when bk (bytevector-fill! bk 0))))))))))) (def (blockstore-write-block! bs block-num payload-bv) ;; Encrypts payload and writes to disk. payload-bv must be BLOCK-PAYLOAD bytes. --- a/src/mount_helper.c +++ b/src/mount_helper.c @@ -28,6 +28,7 @@ * Process tree inspection: * int jerboa_fuse_getpid(void) * int jerboa_fuse_getppid_of(int pid) + * int64_t jerboa_fuse_get_start_time(int pid) */ #include <errno.h> @@ -448,6 +449,15 @@ int jerboa_fuse_getppid_of(int pid) { return (int)kp.ki_ppid; } +int64_t jerboa_fuse_get_start_time(int pid) { + struct kinfo_proc kp; + size_t len = sizeof(kp); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; + if (sysctl(mib, 4, &kp, &len, NULL, 0) < 0) return -1; + if (len == 0) return -1; + return (int64_t)kp.ki_start.tv_sec; +} + #elif defined(LINUX) /* Get parent PID by reading /proc/<pid>/stat. Returns -1 on error. */ @@ -475,6 +485,34 @@ int jerboa_fuse_getppid_of(int pid) { return ppid; } +int64_t jerboa_fuse_get_start_time(int pid) { + if (pid <= 0) { + errno = EINVAL; + return -1; + } + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + FILE *f = fopen(path, "r"); + if (!f) return -1; + char buf[512]; + size_t n = fread(buf, 1, sizeof(buf) - 1, f); + fclose(f); + if (n == 0) return -1; + buf[n] = '\0'; + char *p = strrchr(buf, ')'); + if (!p) return -1; + /* Fields after ')': state(3) ppid(4) pgrp(5) session(6) tty_nr(7) + tpgid(8) flags(9) minflt(10) cminflt(11) majflt(12) cmajflt(13) + utime(14) stime(15) cutime(16) cstime(17) priority(18) nice(19) + num_threads(20) itrealvalue(21) starttime(22) */ + long long starttime = -1; + if (sscanf(p + 2, + "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u " + "%*u %*u %*d %*d %*d %*d %*d %*d %lld", + &starttime) != 1) return -1; + return (int64_t)starttime; +} + #elif defined(DARWIN) int jerboa_fuse_getppid_of(int pid) { @@ -488,6 +526,17 @@ int jerboa_fuse_getppid_of(int pid) { return (int)info.pbi_ppid; } +int64_t jerboa_fuse_get_start_time(int pid) { + if (pid <= 0) { + errno = EINVAL; + return -1; + } + struct proc_bsdinfo info; + int ret = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, sizeof(info)); + if (ret <= 0) return -1; + return (int64_t)info.pbi_start_tvsec; +} + #else int jerboa_fuse_getppid_of(int pid) { @@ -495,6 +544,11 @@ int jerboa_fuse_getppid_of(int pid) { return -1; } +int64_t jerboa_fuse_get_start_time(int pid) { + (void)pid; + return -1; +} + #endif /* ====================================================================