← All Disclosures

Summary

A regression in Ada Language Server made LSP.Known_Requests.Remove_Request set a removal flag without dispatching the completed message to the visitor that removes it from the request map. Completed requests therefore remained in the map after their objects were freed.

This produced three related weaknesses: the map grew for the lifetime of the server (CWE-401/CWE-770), a later $/cancelRequest could write through a dangling pointer (CWE-416), and reuse of a completed request ID raised CONSTRAINT_ERROR and stopped the server (CWE-617). The issue was reachable by an LSP client, normally an editor or extension running on the same system.

Affected: Ada Language Server from commit e71b3dbe (2023-08-29) through release 2026.2.202604091; also confirmed on master at d0547abf.
Fixed: upstream commit f826683c (2026-06-02), included in release 2026.3.202607051 and later.

Demonstration

Against the released 2026.2.202604091 Linux binary, the proof of concept sent a request, waited for its response, and then reused the same JSON-RPC ID. Because the first request was still in the map, Hashed_Maps.Insert raised CONSTRAINT_ERROR; the input task stopped and the server exited nonzero. A second test reused an ID after 50 intervening requests, proving that completed entries persisted. On an AddressSanitizer build, sending $/cancelRequest for a completed request exposed the stale entry as a heap use-after-free write.

How Pragmatic Found It

Pragmatic's UncheckedConversion and UncheckedAccess checks highlighted the conversion and stored Unchecked_Access value in lsp-known_requests.adb. Manual lifetime tracing followed that pointer from insertion through the server's destroy queue and found that Remove_Request never invoked the visitor needed to remove it before Free. Protocol-level tests then confirmed the persistent map, duplicate-ID crash, unbounded retention, and use-after-free path.

The Fix

AdaCore commit f826683c makes Remove_Request dispatch the completed message so the removal branch actually runs. It also changes Delete to Exclude, changes Insert to Include, prevents the cancellation visitor from writing while removal is in progress, and adds a regression test that reuses a request ID. Together these changes remove completed entries before their request objects are freed and prevent duplicate IDs from terminating the server.

Acknowledgements

Thank you to AdaCore Product Security for assessing and fixing the report, to Max Reznik for the patch and regression test, and to Thomas Serabian for coordinating the response and reporter credit.

Severity

CVSS 4.0 Base Score: 8.4 (High)

CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

The score reflects the usual local editor/LSP trust boundary. An installation that deliberately exposes the language server over a network should reassess the attack vector for that deployment.

References

Disclosure Timeline