fix(xbuild): keep a separate .d file per output
What changed, and why it matters
This is a build-system bug fix, not a security vulnerability. The change fixes how dependency files are named during compilation so that incremental builds correctly detect when generated header files change. Previously, two build stages could overwrite each other's dependency tracking files, causing stale object files and incorrect internal string indexes in the MicroPython runtime. There is no direct exploit path from this commit.
No security action required. Treat as a normal build-system reliability fix. If desired, verify that incremental builds now correctly rebuild `py/qstr.o` after adding a new qstr.
Security signals we found
No security-relevant code paths are modified.
No input validation, cryptography, memory management, or authentication logic is touched.
The bug could theoretically cause a firmware build to contain stale/inconsistent qstr indexes, but no exploit mechanism is described or evident.
Commit message and diff contain no security keywords or threat model.
Evidence from the diff
The patch changes the xbuild Rust code that invokes the C/C++ compiler. Previously, dependency files (.d) were named by replacing the output file’s extension (e.g., foo.o -> foo.d). Because the same C source is compiled twice—once to an object file and once for the QSTR (MicroPython interned string) collection pass—the two stages shared a single .d file. The QSTR pass runs with NO_QSTR and does not include the generated qstrdefs.h, so its .d overwrote the object’s .d. On incremental builds, changes to the generated qstr header were not seen for py/qstr.o, causing stale object files and off-by-one qstr indexes. The fix appends .d to the full output name (e.g., qstr.o.d) so each stage keeps its own dependency file. This is a correctness/reliability fix in the build system with no demonstrated security impact.
Changed components
core/embed/xbuild/src/clibrary/compile.rsInspect captured patch +4 / −2
### core/embed/xbuild/src/clibrary/compile.rs
@@ -159,12 +159,14 @@ impl CLibrary {
let input = join_paths_lexically(&base_dir, &src);
let output = derive_output_path(&base_dir, &src, &out_dir, output_type.extension());
- // Only generate .d files for object files compiled from C/C++ sources
+ // Only generate .d files for object files compiled from C/C++ sources.
+ // The output's extension is kept (e.g. `qstr.o.d`), so outputs of
+ // the same source in another stage don't share one .d file.
let cc_dep = if src
.extension()
.is_some_and(|ext| ext == "c" || ext == "cpp" || ext == "cc")
{
- Some(output.with_extension("d"))
+ Some(output.with_added_extension("d"))
} else {
None
};Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.