Raising this as a versioning question rather than a bug report, since the right fix is yours to pick. It is not a vulnerability, and there is no split-brain.
What changed
transport::open::ext::Shm is zextz64!(0x2, false) in 1.9.0 and zextzbuf!(0x2, false) in 1.10.0. VERSION is 0x09 in both, and PatchType::CURRENT is 1 in both.
Extension dispatch matches on iext::eid(header), which is header & !FLAG_Z and therefore keeps the ENC bits, while ext::Shm::ID has ENC baked in. Each version consequently sees the other's OPEN Shm extension as an unknown non-mandatory extension and skips it. The rest of the OpenSyn decodes normally.
Why it is reachable rather than theoretical
The INIT stage is unchanged between the two releases: InitSyn { alice_segment } and InitAck { alice_challenge, bob_segment } are identical, and INIT's Shm extension is zextzbuf in both. A mixed 1.9/1.10 pair therefore negotiates SHM all the way through INIT, both sides allocate and open each other's auth segments, and only then is the OPEN extension dropped.
Both versions' recv_open_syn return Ok(()) when the extension is absent, and the skip is logged at debug!, so the visible outcome is shared memory quietly not being used. Neither side can conclude SHM is on by itself, so both settle on off.
Reproducer
One script, two small crates, no daemon needed:
https://gist.github.com/temporaryfix/0a9c66a733e8c68419417dc0dace7ada
The first two cases are positive controls. If either reports ABSENT then shared-memory is disabled somewhere and the cross-version results below mean nothing.
[1.10.0] Shm::ID = 0x42 VERSION = 0x09 PatchType::CURRENT = 1
[1.9.0] Shm::ID = 0x22 VERSION = 0x09 PatchType::CURRENT = 1
1.9.0 encodes -> 1.9.0 decodes ext_shm = PRESENT (positive control)
1.10.0 encodes -> 1.10.0 decodes ext_shm = PRESENT (positive control)
1.10.0 encodes -> 1.9.0 decodes decoded OK, ext_shm = ABSENT
1.9.0 encodes -> 1.10.0 decodes decoded OK, ext_shm = ABSENT
The whole difference is one header byte: 0x42 (ENC_ZBUF | 0x2) against 0x22 (ENC_Z64 | 0x2).
Where this lives
| what |
where |
pub type Shm = zextz64!(0x2, false) |
zenoh-protocol-1.9.0/src/transport/open.rs:111 |
pub type Shm = zextzbuf!(0x2, false) |
zenoh-protocol-1.10.0/src/transport/open.rs:111 |
INIT's Shm is zextzbuf in both |
src/transport/init.rs:150 |
VERSION = 0x09 in both |
src/lib.rs:31 |
PatchType::CURRENT = Self(1) in both |
src/transport/mod.rs:326 |
eid(header) = header & !FLAG_Z, so ENC is part of the match key |
src/common/extension.rs:68 |
the ext::Shm::ID dispatch arm |
zenoh-codec/src/transport/open.rs:175 |
unknown and non-mandatory, so skipped with a debug! |
zenoh-codec/src/common/extension.rs:33-41 |
a missing extension returns Ok(()) |
zenoh-transport-1.9.0/src/unicast/establishment/ext/shm.rs:481 |
| the same in 1.10.0 |
zenoh-transport-1.10.0/src/unicast/establishment/ext/shm/auth.rs:544 |
The question
What should a decoder do when a known extension id arrives with an unexpected ENC? Today it is indistinguishable from an unknown extension, so any future ENC change on any id degrades silently between peers that have agreed on both VERSION and PatchType::CURRENT.
If the intended rule is to bump PatchType::CURRENT whenever an extension's encoding changes, that seems worth writing down somewhere a later change will run into it.
Not tested
I have not run two daemons against each other. The reproducer works at the codec level, which is where the dispatch happens. Happy to do the daemon version if that would be more useful.
Written with AI assistance. Every claim above was checked against the sources named and by running the reproducer.
Raising this as a versioning question rather than a bug report, since the right fix is yours to pick. It is not a vulnerability, and there is no split-brain.
What changed
transport::open::ext::Shmiszextz64!(0x2, false)in 1.9.0 andzextzbuf!(0x2, false)in 1.10.0.VERSIONis0x09in both, andPatchType::CURRENTis1in both.Extension dispatch matches on
iext::eid(header), which isheader & !FLAG_Zand therefore keeps the ENC bits, whileext::Shm::IDhas ENC baked in. Each version consequently sees the other's OPENShmextension as an unknown non-mandatory extension and skips it. The rest of theOpenSyndecodes normally.Why it is reachable rather than theoretical
The INIT stage is unchanged between the two releases:
InitSyn { alice_segment }andInitAck { alice_challenge, bob_segment }are identical, and INIT'sShmextension iszextzbufin both. A mixed 1.9/1.10 pair therefore negotiates SHM all the way through INIT, both sides allocate and open each other's auth segments, and only then is the OPEN extension dropped.Both versions'
recv_open_synreturnOk(())when the extension is absent, and the skip is logged atdebug!, so the visible outcome is shared memory quietly not being used. Neither side can conclude SHM is on by itself, so both settle on off.Reproducer
One script, two small crates, no daemon needed:
https://gist.github.com/temporaryfix/0a9c66a733e8c68419417dc0dace7ada
The first two cases are positive controls. If either reports ABSENT then
shared-memoryis disabled somewhere and the cross-version results below mean nothing.The whole difference is one header byte:
0x42(ENC_ZBUF | 0x2) against0x22(ENC_Z64 | 0x2).Where this lives
pub type Shm = zextz64!(0x2, false)zenoh-protocol-1.9.0/src/transport/open.rs:111pub type Shm = zextzbuf!(0x2, false)zenoh-protocol-1.10.0/src/transport/open.rs:111Shmiszextzbufin bothsrc/transport/init.rs:150VERSION = 0x09in bothsrc/lib.rs:31PatchType::CURRENT = Self(1)in bothsrc/transport/mod.rs:326eid(header) = header & !FLAG_Z, so ENC is part of the match keysrc/common/extension.rs:68ext::Shm::IDdispatch armzenoh-codec/src/transport/open.rs:175debug!zenoh-codec/src/common/extension.rs:33-41Ok(())zenoh-transport-1.9.0/src/unicast/establishment/ext/shm.rs:481zenoh-transport-1.10.0/src/unicast/establishment/ext/shm/auth.rs:544The question
What should a decoder do when a known extension id arrives with an unexpected ENC? Today it is indistinguishable from an unknown extension, so any future ENC change on any id degrades silently between peers that have agreed on both
VERSIONandPatchType::CURRENT.If the intended rule is to bump
PatchType::CURRENTwhenever an extension's encoding changes, that seems worth writing down somewhere a later change will run into it.Not tested
I have not run two daemons against each other. The reproducer works at the codec level, which is where the dispatch happens. Happy to do the daemon version if that would be more useful.
Written with AI assistance. Every claim above was checked against the sources named and by running the reproducer.