ava-labs / ava-labs/Simplex

WAL and last-record files created with 0666 permissions, forgeable CRC64 integrity

Open
#561 0 comments 0 reactions 0 assignees View on GitHub
medium security
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.