What
inodeAttrs in internal/fusefs/fuse_unix.go takes size uint64 and every caller that threads a stored size through it does an unchecked uint64(size) cast on a value that is signed int64 everywhere else in the codebase. If that signed value is ever negative, the cast wraps and the mount publishes a huge st_size to the kernel instead of rejecting or clamping.
Why it's a problem
size_bytes is stored as a SQLite INTEGER in both internal/snapshot/store.go and internal/overlay/store.go, scanned back into Go as int64, and threaded through resolver.Getattr as int64. Nothing on the read path verifies that the value is non-negative before it reaches the FUSE attr boundary.
Concrete trip points are:
LookUpInode at internal/fusefs/fuse_unix.go:198
GetInodeAttributes at internal/fusefs/fuse_unix.go:219
SetInodeAttributes at internal/fusefs/fuse_unix.go:246
All three do inodeAttrs(mode, uint64(size), typ, mtime). The final assignment lands in fuseops.InodeAttributes.Size (uint64). So int64(-1) becomes st_size = 18446744073709551615.
The hunt notes persistent snapshot and overlay state as a trust boundary, which makes the unchecked conversion the actual bug rather than "garbage in, garbage out."
Repro
Manually set a negative size_bytes row in either database (or simulate one in a resolver.Getattr stub), then stat the path through the mount. The reported st_size is ~18 EB instead of 0 or an error.
Proposed fix
Change inodeAttrs to take a signed int64 and clamp negatives to 0 inside the function. All five existing call sites already have integer literals or int64 variables, so the casts disappear. The existing "dir with size 0 reports 4096" behavior is preserved. PR to follow.
What
inodeAttrsininternal/fusefs/fuse_unix.gotakessize uint64and every caller that threads a stored size through it does an uncheckeduint64(size)cast on a value that is signedint64everywhere else in the codebase. If that signed value is ever negative, the cast wraps and the mount publishes a hugest_sizeto the kernel instead of rejecting or clamping.Why it's a problem
size_bytesis stored as a SQLiteINTEGERin bothinternal/snapshot/store.goandinternal/overlay/store.go, scanned back into Go asint64, and threaded throughresolver.Getattrasint64. Nothing on the read path verifies that the value is non-negative before it reaches the FUSE attr boundary.Concrete trip points are:
LookUpInodeatinternal/fusefs/fuse_unix.go:198GetInodeAttributesatinternal/fusefs/fuse_unix.go:219SetInodeAttributesatinternal/fusefs/fuse_unix.go:246All three do
inodeAttrs(mode, uint64(size), typ, mtime). The final assignment lands infuseops.InodeAttributes.Size(uint64). Soint64(-1)becomesst_size = 18446744073709551615.The hunt notes persistent snapshot and overlay state as a trust boundary, which makes the unchecked conversion the actual bug rather than "garbage in, garbage out."
Repro
Manually set a negative
size_bytesrow in either database (or simulate one in aresolver.Getattrstub), thenstatthe path through the mount. The reportedst_sizeis~18 EBinstead of 0 or an error.Proposed fix
Change
inodeAttrsto take a signedint64and clamp negatives to 0 inside the function. All five existing call sites already have integer literals orint64variables, so the casts disappear. The existing "dir with size 0 reports 4096" behavior is preserved. PR to follow.