WAL and last-record files created with 0666 permissions, forgeable CRC64 integrity
- Dominant language
- Go
- Stars
- 22
- Forks
- 4
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 34
Description
## Details
Every file the WAL package creates — the append-only consensus log (wal.go) and the last-record crash-recovery file (last_record.go) — is opened with mode 0666. The effective permissions are 0666 masked by the process umask: under the common 022 umask the files are world-readable; under 002 they are group-writable; under a 0 umask (not unusual for daemons/containers that set it explicitly) they are world-writable.
These files are the node's durable consensus safety state: signed votes, notarizations, finalizations, and block records that are replayed verbatim into the consensus engine at startup (Epoch.restoreFromWal), and the last-record file that overrides the replayed state. The only integrity mechanism on the stored records is a CRC64/ECMA checksum, which is a public, linear, non-cryptographic function — any party able to write the file can recompute valid checksums for arbitrary record bytes.
Concrete exploit: on a multi-user host (or a container/host where another compromised low-privilege service shares the filesystem) with a permissive umask, a local unprivileged user opens the WAL file, replaces or appends records (e.g., deletes the record showing the node voted for block B in round r, or injects a fabricated record), recomputes the CRC64 framing, and waits for the node to restart. Replay accepts the forged log wholesale — record framing and CRC both validate — so the attacker controls the node's remembered consensus state, e.g. inducing it to re-vote (equivocate) or to resume from attacker-chosen rounds. Even with the default 022 umask, the world-readable mode discloses the node's full vote/notarization history to any local account.
Since the files carry security-critical state, they should be created 0600 (or the package should accept a caller-specified restrictive mode) rather than delegating protection entirely to the deployment's umask.
## Evidence
1. [wal/wal.go:12–14](https://github.com/ava-labs/Simplex/blob/main/wal/wal.go#L12-L14)
WalPermissions is 0666: the consensus WAL file is requested world-readable and world-writable; the effective mode is only reduced by the process umask (commonly 022, still leaving the file world-readable; umask 002/000 leaves it group/world-writable).
2. [wal/wal.go:34](https://github.com/ava-labs/Simplex/blob/main/wal/wal.go#L34)
os.OpenFile creates the WAL file with the 0666 mode on first append/read.
3. [wal/last\_record.go:75](https://github.com/ava-labs/Simplex/blob/main/wal/last_record.go#L75)
The last-record side file — the crash-recovery anchor that overrides WAL state at startup — is also created with WalPermissions (0666).
4. [wal/last\_record.go:107–109](https://github.com/ava-labs/Simplex/blob/main/wal/last_record.go#L107-L109)
Truncate() re-creates the last-record file with the same 0666 mode.
5. [wal/record.go:26–34](https://github.com/ava-labs/Simplex/blob/main/wal/record.go#L26-L34)
Record integrity is protected only by CRC64/ECMA, which is non-cryptographic and computable by anyone; a local user with write access can rewrite or inject records that pass all replay validation.
## Impact
Confidentiality LOW: WAL contents (votes, blocks) are largely public consensus data, so local read exposure is limited. Integrity HIGH in the writable-umask case: full forgery of replayed consensus state, since CRC64 provides no protection against deliberate modification. Availability LOW: a local writer can also corrupt the log, triggering truncation/crash paths.
## Reproduction steps
1. Requires a local account (or co-resident compromised service) on the node's host — PR LOW, AV LOCAL. Read access works under the default 022 umask; write/tamper access additionally requires the daemon to run with a permissive umask (002/000) and a restart of the node to replay the forged log — deployment conditions, so AT PRESENT. No user interaction; no defenses to defeat (CRC64 is trivially forgeable).
## Recommended fix
Security-critical WAL and last-record files are created with permissive 0666 mode, relying on the deployment umask; combined with the forgeable CRC64 framing, any principal with write access can fabricate valid consensus replay state. Fix criteria: Files holding consensus state must be created with owner-only permissions (0600) or an explicit caller-provided restrictive mode, independent of umask. Verified by creating a WAL under a 0 umask and asserting the resulting file mode grants no group/other access.
---
**Severity:** MEDIUM
**Status:** Open
**Category:** Incorrect permissions
**CWE:** [CWE-276](https://cwe.mitre.org/data/definitions/276.html)
**Repository:** ava-labs/Simplex
**Branch:** main
**Date created:** 2026-08-21
---
Contributor guide
No contributing guide indexed for this repository
Research direction
Read wal/wal.go and wal/last_record.go to trace how the WAL and last-record files are created and recreated. Run the WAL package tests, then verify under a zero umask that newly created consensus-state files grant no group or other access; done means both creation paths meet that permission criterion.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- distributed-systems, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100