CNA-BUG-270: The @param lines of cna_post_process_chain_add_owned_pass and cna_skybox_set_owned_environment say the consumed handle is invalid on return even when the call fails, but both routes release it only after every check that can fail
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
engine_layer.h says the pass and environment handles are 'invalid on return whether or not the call succeeded', but both routes release them only after their checks pass, so a binding that follows the @param line leaks the handle after a refused call.
- Identifier
CNA-BUG-270- Category
- Bug
- Subsystem
- C API & bindings
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- cna_post_process_chain_add_owned_pass and cna_skybox_set_owned_environment (CNA/C/engine_layer.h)
Expected behaviour
The descriptions in the same header say the handle is consumed on success and, for cna_post_process_effect_pass_create_owning, that on failure the caller keeps the handle; docs/c-api/OWNERSHIP.md defines a transferred parameter as consumed on successful return with ownership unchanged on failure.
Actual behaviour at TARGET
The @param line of cna_post_process_chain_add_owned_pass says the pass is “invalid on return whether or not the call succeeded”, and that of cna_skybox_set_owned_environment says the same of the environment. The implementations return an error for an invalid pass, a pass that is still lending its effect, an invalid skybox or a handle that is not a cube map before they release anything, and the skybox route notes that the release is “the LAST thing here that can fail, so a refusal never leaves the caller holding nothing”. After such a refusal the caller still holds a valid handle. A binding that follows the @param line and never releases the handle after a failed call leaks it; one that follows the description releases it correctly.
Source locations
modules/c-api/include/CNA/C/engine_layer.h— the two @param lines that contradict their own descriptionsmodules/c-api/src/CnaCApiEngineLayer.cpp— both routes release the handle only after their checks passdocs/c-api/OWNERSHIP.md— defines the transferred category: failure leaves ownership unchanged
Evidence
Read at 009d40f5; nothing was built or run. Found by the C13 conservation reviewer (native C API appendix) and re-verified by the orchestrator against the header text and the two implementations.
Independent re-verification: Read at 009d40f5; nothing was built or run. Found by the C13 conservation reviewer (native C API appendix) and re-verified against the header text and the two implementations. Current tests, corrected: EngineLayerSmoke.c calls both routes. It checks that a successful hand-over consumes the handle (a second release fails) and that CNA_INVALID_HANDLE is refused, but no test hands over a valid handle that a route refuses (a pass that is lending its effect, a handle that is not a cube map) and then checks that the handle is still valid.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
EngineLayerSmoke.c (modules/c-api/tests/pure_c) calls both routes: it checks that a successful hand-over consumes the handle (a second release fails) and that CNA_INVALID_HANDLE is refused. No test hands over a valid handle that a route refuses (a lending pass, a handle that is not a cube map) and then checks that the handle is still valid.
Regression test
Call each route with a valid handle and an invalid target (an invalid chain, a lending pass, a non-cube handle), expect an error and a still-valid handle, and correct the two @param lines to say so.
Blast radius
Language bindings generated from the header text: a binding that trusts the @param line leaks one handle per refused call (for the pass and skybox routes only). Successful calls are unaffected.
Workaround
Follow the descriptions and OWNERSHIP.md: treat the handle as consumed only when the call returns success, and release it yourself otherwise.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- C API
- Deep dives
- Native C API
- Known issues
- Bug index