Skip to content

fix(config): don't panic on a corrupt config, and don't leave keys wo… - #63

Open
Aporis3674 wants to merge 1 commit into
CluvexStudio:mainfrom
Aporis3674:fix/config-corrupt-panic-and-key-permissions
Open

fix(config): don't panic on a corrupt config, and don't leave keys wo…#63
Aporis3674 wants to merge 1 commit into
CluvexStudio:mainfrom
Aporis3674:fix/config-corrupt-panic-and-key-permissions

Conversation

@Aporis3674

@Aporis3674 Aporis3674 commented Jul 26, 2026

Copy link
Copy Markdown

Two problems in aether/src/config.rs, both reached through the same code path.

1. A corrupt config panics instead of erroring

config::load returns Result, but the conversion it calls does not:

let wg_priv = base64::decode(&p.wg_private_key).expect("decode wg private key");
let mut wg_private_key = [0u8; 32];
wg_private_key.copy_from_slice(&wg_priv);   // panics unless len == 32

Both lines panic on input a user can end up with. Reproduced against ee5a5f5:

panicked at src/config.rs:55:24:
  copy_from_slice: source slice length (20) does not match destination slice length (32)

panicked at src/config.rs:46:14:      # a value that isn't base64

How you get there. config::save uses std::fs::write, which truncates in
place — so a process killed mid-save leaves exactly that truncated file, and
every later start panics instead of re-registering. The user has to work out on
their own that deleting aether.toml fixes it. The caller is already prepared
for an error; it cannot catch a panic.

client_id, a few lines below, is length-checked. These two fields just
missed the same guard.

2. The key file is world-readable

aether.toml holds the WireGuard private key, the MASQUE client private key and
the access token. std::fs::write creates it with the process umask — 0644 on
a typical Linux box — and there is no permission handling anywhere in the tree:

$ grep -rn "set_permissions\|PermissionsExt\|0o600\|OpenOptions" aether/src
$

The change

  • TryFrom replaces From, and decode_key names the field that is wrong:
    config: wg_private_key decodes to 20 bytes, expected 32
  • the config is written to a temp file, chmod 0600, then renamed. That fixes
    the mode and makes the write atomic, so the truncation behind the panic
    can no longer happen in the first place
  • five tests: both malformed shapes, an oversized key, the happy path, and the
    resulting file mode
cargo check           Finished dev profile in 55.21s
cargo test config::   5 passed

config.rs is left rustfmt-clean. No behaviour change for a valid config.


Generated by Claude Code

…rld-readable

Loading an identity went through `impl From<PersistedIdentity> for Identity`,
which decoded the two WireGuard keys with `.expect()` and then
`copy_from_slice()` into `[u8; 32]`. Both panic on input the user can
easily end up with:

  $ cargo test bug_repro -- --nocapture
  panicked at src/config.rs:55:24:
    copy_from_slice: source slice length (20) does not match destination
    slice length (32)
  panicked at src/config.rs:46:14:            # non-base64 value

`config::save` writes with `std::fs::write`, which truncates in place, so a
process killed mid-save leaves exactly that truncated file — and every later
start panics instead of re-registering. `config::load` already returns
`Result`, so the caller is prepared for an error; it cannot catch a panic.
The `client_id` field a few lines below is length-checked, which is the
pattern the two key fields were missing.

- `TryFrom` replaces `From`, and `decode_key` names the offending field:
  "config: wg_private_key decodes to 20 bytes, expected 32"
- the config holds a WireGuard private key, a MASQUE client private key and
  an access token, and was created with the process umask — 0644 on a
  typical Linux box. It is now written to a temp file, chmod 0600, then
  renamed, which fixes the world-readable mode and makes the write atomic
  so the truncation cannot happen in the first place
- five tests cover both malformed shapes, an oversized key, the happy path
  and the resulting file mode
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant