Skip to content

Bound native group keys and channels - #256

Merged
highesttt merged 1 commit into
mainfrom
highest/evict-group-keys
Oct 5, 2026
Merged

highesttt merged 1 commit into
mainfrom
highest/evict-group-keys

Conversation

@highesttt

@highesttt highesttt commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

LTSM has a fixed 16 MiB heap. A session still keeps the native key and channels of every group key it unwraps, and nothing ever destroys them. A session that sees enough distinct group keys exhausts the heap and aborts with ltsm: WASM abort called.

This keeps at most 4096 native group keys and channels (about 3 MB) and evicts the least recently used. Eviction destroys the native key and every channel created from it through the new Runner.KeyDestroy. The connector already fetches and unwraps a missing group key on demand (fetchAndUnwrapGroupKey on a decrypt miss and before every send), so an evicted key is reloaded the next time it is needed.

DecryptGroupMessage and EncryptGroupMessageRaw now hold Manager.mu from the key lookup through the native call, so an eviction cannot destroy the channel they are using. Native calls already serialize on the runner's own mutex. The ZIP retry in sendMessage now refetches the group key and retries once, like the main send path, and surfaces refetch failures the same way, because a key can be evicted between the two sends.

This relies on #252 (merged): before it, native unwrap leaked about 0.74 KB per call even after the key was destroyed, so eviction could not bound the heap.

#253 and #254 also change Runner and Manager, so whichever of them and this lands second needs a small rebase. In particular KeyDestroy has to follow #253's failure and discard handling.

Testing

Local, synthetic keys and messages only. No tests are added to the diff; the checks below ran outside the repo through a Go overlay. Each loop iteration unwraps a group key, then encrypts and decrypts a message and checks the plaintext.

Source Same group key, 20,000 iterations New group key every iteration
main with #250-#252 heap flat aborts at iteration 6455
this PR heap flat 20,000 iterations, 2051 keys and 2050 channels retained, heap flat at 11.08 MB

go test ./pkg/... and go test -race ./pkg/e2ee ./pkg/connector/... pass. A separate stress run with the cap lowered to 6 has four goroutines unwrap, encrypt and decrypt while keys are evicted. It sees only "key not loaded" or "not found" errors, passes three times under -race, and fails with unknown channel against a version of manager.go that released the lock before the native call.

Limits

  • Not run against a live account. The synthetic loop covers the allocation path, not how many distinct group keys real accounts reach.
  • The cap counts objects, not bytes. The channel to each group key's creator is cached once per creator and is not counted.
  • Each eviction marks the crypto copy dirty, so the next channel operation refreshes the 16 MiB copy. This is a small cost per new group key once the cap is reached.
  • Group encrypt and decrypt now hold Manager.mu for the duration of the native call, so other Manager methods wait behind them.

@indent

indent Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Issues

All clear! No issues remaining. 🎉

3 issues already resolved
  • Still present after the rebase. DecryptGroupMessage (line 658) and EncryptGroupMessageRaw (line 753) release m.mu after ensureGroupChannel, so a concurrent eviction can destroy the channel and the call fails once with unknown channel. It is not memory-unsafe and recovers after a key refetch. (fixed by commit 1868051)
    Found by Indent Review Agent
  • When LINE rejects a file with "Extension does not support file upload", sendMessage in pkg/connector/send_message.go (~line 749) zips, re-uploads and calls E2EEncryptGroupMessageRaw without fetchAndUnwrapGroupKey or a retry. If the key was evicted during the first send and zip upload, the send fails with "group key not loaded". Before this PR a loaded key never disappeared. (fixed by commit 1868051)
    Found by Indent Review Agent
  • The new ZIP retry in send_message.go ignores the error from fetchAndUnwrapGroupKey. If the refetch fails, the user gets a generic "failed to encrypt zipped file message: group key not loaded" instead of the ErrAbort or reconnect-required status the main send path returns. (fixed by commit 004cd1a)
    Found by Indent Review Agent at dc51cc6

CI Checks

All CI checks passed on 004cd1a.

Review agents

Select any unchecked box below to run or rerun that agent.

Passed (1)
  • Indent Review Agent · ZIP-retry fetch errors now match the main send path; no issues remain at this head.
Full results

Indent Review Agent

  • Summary: ZIP-retry fetch errors now match the main send path; no issues remain at this head.
  • Last ran on commit: 004cd1a3
  • Latest result
    {
      "summary": "ZIP-retry fetch errors now match the main send path; no issues remain at this head.",
      "findings": []
    }

@highesttt
highesttt force-pushed the highest/evict-group-keys branch from d764156 to dc51cc6 Compare October 5, 2026 16:05
@highesttt
highesttt changed the base branch from iFixRobots/own-line-crypto-runtimes to iFixRobots/fix-native-group-key-ownership October 5, 2026 16:05
@highesttt
highesttt force-pushed the highest/evict-group-keys branch 2 times, most recently from 1868051 to 451152c Compare October 5, 2026 16:37
Base automatically changed from iFixRobots/fix-native-group-key-ownership to main October 5, 2026 16:41
Comment thread pkg/connector/send_message.go
LTSM has a fixed 16 MiB heap and the manager kept the native key and
channels of every group key it had ever unwrapped. A session that saw
enough distinct group keys exhausted the heap and aborted with
"ltsm: WASM abort called".

Keep at most 4096 native group keys and channels, evicting the least
recently used. Eviction destroys the native key and every channel created
from it. The connector already fetches and unwraps a missing group key on
demand, so an evicted key is reloaded the next time it is needed.
@highesttt
highesttt force-pushed the highest/evict-group-keys branch from 451152c to 004cd1a Compare October 5, 2026 16:55
@highesttt
highesttt merged commit e017200 into main Oct 5, 2026
13 checks passed
@highesttt
highesttt deleted the highest/evict-group-keys branch October 5, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant