Repository navigation
Reject null write buffers before opening or truncating files #738
Description
Problem
A positive-length null source is detected only by the OS write after the destination has already been opened. fs_write_all therefore destroys existing contents before returning an error; fs_append_all can create a new empty file for the same invalid input. Both currently check only negative lengths before opening: std/fs/file.wave and std/fs/file.wave.
Reproduction
Tested at 5002a93a4a233032553aa6c6e3639d88e93c53bf on Fedora Linux 43 amd64 with LLVM 21.1.8. Build the current compiler with LLVM_SYS_211_PREFIX=/usr/lib64/llvm21 cargo build --locked --release --jobs 2.
The audit used a temporary copy of the repository std, changing only its import prefix to avoid an installed stale std. Recreate it from the repository root:
python3 - <<'PYSETUP'
import pathlib, shutil, tempfile
p = pathlib.Path(tempfile.mkdtemp(prefix="wave-fs-audit-"))
shutil.copytree("std", p / "auditstd")
for f in (p / "auditstd").rglob("*.wave"):
f.write_text(f.read_text().replace('"std::', '"auditstd::'))
print(p)
PYSETUPUse the printed path as $audit_dir; save the source below as $audit_dir/repro.wave.
import("auditstd::fs::file")::{fs_write_all};
fun main() {
println("{}", fs_write_all("target.bin", null, 1, 384));
}
target/release/wavec build "$audit_dir/repro.wave" --dep "auditstd=$audit_dir/auditstd" -O0 --out-dir "$audit_dir/out"
python3 - "$audit_dir/out" <<'PYRUN'
import pathlib, subprocess, sys
p = pathlib.Path(sys.argv[1])
f = p / "target.bin"
f.write_bytes(b"preserve me")
r = subprocess.run([str(p / "repro")], cwd=p, capture_output=True, text=True)
print(r.returncode, r.stdout.strip(), repr(f.read_bytes()))
PYRUNRepeat with -O2.
Run in a temporary directory containing target.bin with bytes preserve me. At both O0/O2, the program prints -14 and the file becomes empty. Replacing the imported function and call with fs_append_all preserves existing bytes but creates target.bin if it was absent, then prints -14.
A negative-length control prints IO_ERR_INVALID (-4096) and preserves the file. A zero-length write successfully truncates it; keep that valid behavior.
Scope
Reject src == null && len > 0 before opening a path in fs_write_all and fs_append_all, including their portable write/append wrappers. Return the existing invalid-argument result. This requests only the known-null check, not validation of arbitrary pointer accessibility. Keep zero-length semantics. Difficulty: easy.
Completion criteria
- Positive-length null writes/appends return IO_ERR_INVALID without truncating or creating a file.
- Negative lengths, valid writes/appends and zero-length calls keep their existing behavior.
- Extend the native I/O invalid-buffer fixture and verify both existing and missing destinations.
Activity
fix(std/fs): reject null write buffers before opening files (wavefnd#742