Exoneration record. This issue has been wrong twice; both corrections are
below, because the corrections are the content. NO CODE CHANGE IS REQUIRED.
Layer 1 - recv_file_entry() refusing a non-directory transfer-root entry
(flist.c:1127-1134) - was ported in #7625
(crates/protocol/src/flist/read/mod.rs, read_entry()). That is real and
necessary: it is a policy check on untrusted peer input.
Correction 1: layers 2 and 3 are not unported work
Originally filed as "layers 2 and 3 outstanding". Wrong - asserted from
upstream's C without checking oc's architecture.
Upstream's failure mode is C-specific. dir_flist is a separate array
holding only S_ISDIR entries, grown with realloc() not calloc(). A .
entry with a non-directory mode never lands in it, so dir_flist->used stays
0 while the basename test still passes; parent_ndx keeps its default 0 and
consumers dereference dir_flist->files[0] - uninitialised heap. Layer 2
(flist.c:3072-3083) clears parent_ndx to -1 when dir_flist is empty;
layer 3 bounds-checks the consumers.
oc has no such structure:
- No
dir_flist. crates/transfer/src/generator/ndx_map.rs states it:
"oc has no separate dir_flist, so the flat index is recorded directly and
needs no second translation." The equivalent is NdxSegment::parent_flat: i32, a flat index into the single list.
- Layer 2's semantics already present.
crates/transfer/src/generator/file_list/inc_recurse.rs sets parent_flat
to 0 only when file_list.get(0) exists and is named ., else -1.
Slot 0 is checked present before use.
- Layer 3's semantics already present.
DirectoryTree::try_add_directory
(crates/protocol/src/flist/dir_tree.rs) rejects out-of-range parents; the
unchecked path uses safe Vec indexing, so the worst case is a bounds-check
panic, never an uninitialised read. Already covered by SEC-4 /
CVE-2026-43620 regression tests in that file.
Layers 2 and 3 defend a C memory-layout hazard that safe-Rust flat-Vec
indexing does not express.
Correction 2: the "layer-1 coupling" residual was a FALSE ALARM
The previous revision claimed the name-only test at inc_recurse.rs
(file_list.get(0).is_some_and(|e| e.name() == ".")) was safe only because
the #7625 decode guard guarantees any . entry is a directory - i.e. a
load-bearing cross-module dependency - and proposed making the test
type-aware.
That is wrong. It was reasoned from role names instead of traced. Tracing the
assignment to GeneratorContext::file_list:
generator/file_list/mod.rs build_file_list() is a local filesystem
walk (oc's send_file_list(), citing flist.c:2227). It is the only
producer.
inc_recurse.rs merely mem::takes that same locally-built list and
reorders it; iconv.rs re-encodes it. Neither decodes anything.
- The synthetic
. there is built from std::fs::symlink_metadata(".") and
pushed only inside an explicit if meta.is_dir().
So the generator's . is a directory by construction, from oc's own walk,
gated by its own is_dir() check. The decode guard never touches this list -
consistent with the fact that no role under crates/transfer/src/generator/
constructs a FileListReader at all. The name-only test is safe for a reason
entirely unrelated to #7625, and there is no cross-module coupling.
The proposed type test is therefore not shipped: a hardening added for a
reason that does not hold reads as protection and provides none.
Status
Nothing to port, nothing to harden. Recorded so this is not re-filed a third
time.
Exoneration record. This issue has been wrong twice; both corrections are
below, because the corrections are the content. NO CODE CHANGE IS REQUIRED.
Layer 1 -
recv_file_entry()refusing a non-directory transfer-root entry(
flist.c:1127-1134) - was ported in #7625(
crates/protocol/src/flist/read/mod.rs,read_entry()). That is real andnecessary: it is a policy check on untrusted peer input.
Correction 1: layers 2 and 3 are not unported work
Originally filed as "layers 2 and 3 outstanding". Wrong - asserted from
upstream's C without checking oc's architecture.
Upstream's failure mode is C-specific.
dir_flistis a separate arrayholding only
S_ISDIRentries, grown withrealloc()notcalloc(). A.entry with a non-directory mode never lands in it, so
dir_flist->usedstays0 while the basename test still passes;
parent_ndxkeeps its default 0 andconsumers dereference
dir_flist->files[0]- uninitialised heap. Layer 2(
flist.c:3072-3083) clearsparent_ndxto-1whendir_flistis empty;layer 3 bounds-checks the consumers.
oc has no such structure:
dir_flist.crates/transfer/src/generator/ndx_map.rsstates it:"oc has no separate
dir_flist, so the flat index is recorded directly andneeds no second translation." The equivalent is
NdxSegment::parent_flat: i32, a flat index into the single list.crates/transfer/src/generator/file_list/inc_recurse.rssetsparent_flatto
0only whenfile_list.get(0)exists and is named., else-1.Slot 0 is checked present before use.
DirectoryTree::try_add_directory(
crates/protocol/src/flist/dir_tree.rs) rejects out-of-range parents; theunchecked path uses safe
Vecindexing, so the worst case is a bounds-checkpanic, never an uninitialised read. Already covered by SEC-4 /
CVE-2026-43620 regression tests in that file.
Layers 2 and 3 defend a C memory-layout hazard that safe-Rust flat-
Vecindexing does not express.
Correction 2: the "layer-1 coupling" residual was a FALSE ALARM
The previous revision claimed the name-only test at
inc_recurse.rs(
file_list.get(0).is_some_and(|e| e.name() == ".")) was safe only becausethe #7625 decode guard guarantees any
.entry is a directory - i.e. aload-bearing cross-module dependency - and proposed making the test
type-aware.
That is wrong. It was reasoned from role names instead of traced. Tracing the
assignment to
GeneratorContext::file_list:generator/file_list/mod.rsbuild_file_list()is a local filesystemwalk (oc's
send_file_list(), citingflist.c:2227). It is the onlyproducer.
inc_recurse.rsmerelymem::takes that same locally-built list andreorders it;
iconv.rsre-encodes it. Neither decodes anything..there is built fromstd::fs::symlink_metadata(".")andpushed only inside an explicit
if meta.is_dir().So the generator's
.is a directory by construction, from oc's own walk,gated by its own
is_dir()check. The decode guard never touches this list -consistent with the fact that no role under
crates/transfer/src/generator/constructs a
FileListReaderat all. The name-only test is safe for a reasonentirely unrelated to #7625, and there is no cross-module coupling.
The proposed type test is therefore not shipped: a hardening added for a
reason that does not hold reads as protection and provides none.
Status
Nothing to port, nothing to harden. Recorded so this is not re-filed a third
time.