Skip to content

cvd host_bugreport: don't discard the whole archive on a directory entry - #3211

Open
shuowanghsu wants to merge 1 commit into
google:mainfrom
shuowanghsu:netsimd-bugreport-eisdir
Open

shuowanghsu wants to merge 1 commit into
google:mainfrom
shuowanghsu:netsimd-bugreport-eisdir

Conversation

@shuowanghsu

Copy link
Copy Markdown
Contributor

AddFileAt guarded its input only with FileExists, which is a stat check and therefore succeeds for anything that exists, including directories. libzip does not read the source until WritableZip::Finalize, where reading a directory fails with EISDIR. libzip then calls zip_source_rollback_write, which discards the entire archive rather than just the offending entry.

AddNetsimdLogs hits this: it lists the netsimd temp directory with a non-recursive DirectoryContents, so when netsimd runs with --pcap it hands the pcap/ directory straight to AddFileAt. The result is that cvd host_bugreport produces an archive containing almost nothing, and the netsimd capture files are never collected either.

Against the unpatched library the new test reproduces it exactly:

Expected "zip_close(raw_zip)" == "0" but was -1 vs 0. Read error: Is a directory

Changes

  1. Require a regular file in AddFileAt. This is the important one — today any caller that passes a directory silently destroys the archive it was building, and does so at finalization time, far from the offending call site. After this change a bad entry can only lose itself. The check is S_ISREG rather than just a directory test, because a FIFO or a socket reaches Finalize the same way, and opening one there can fail or block indefinitely.
  2. Collect netsimd logs recursively, naming each entry by its path relative to the netsimd directory, so the capture files under pcap/ are actually included. Per-entry errors are logged rather than returned, so one bad file cannot abort the rest of the walk.
  3. Add WalkDirectoryFiles next to the existing WalkDirectory in common/libs/utils/files.h, reporting only non-directories. Symlinks are not followed when deciding whether to recurse, so a symlink loop cannot trap the walk.
  4. Add regression tests for both halves: that AddFileAt rejects directories and FIFOs, that a rejected entry no longer takes unrelated entries down with it at Finalize, and that WalkDirectoryFiles recurses while WalkDirectory keeps reporting directories.

Notes

  • WalkDirectoryFiles is a new sibling rather than a change to WalkDirectory, because SymlinkHostPackage, WriteFsConfig and LinkOrCopyDirectoryContentsRecursively all rely on WalkDirectory invoking the callback for directories. WriteFsConfig in particular would silently drop directory entries from filesystem_config.txt. Happy to consolidate them differently if you'd prefer.
  • logs/, tombstones/ and recording/ are left non-recursive. They are flat in practice, and after change 1 an unexpected subdirectory there is merely logged and skipped rather than fatal. Can extend WalkDirectoryFiles to them if you'd like uniformity.
  • Per-entry errors remain non-fatal, consistent with the existing contract that "a partial bug report is still useful."

Testing

bazel test  //cuttlefish/host/libs/zip:zip_file_test \
            //cuttlefish/common/libs/utils:files_test
bazel build //cuttlefish/host/commands/host_bugreport:cvd_internal_host_bugreport

All new test cases pass with the fix; the zip_file_test cases fail without it.

`AddFileAt` guarded its input only with `FileExists`, which is a `stat`
check and therefore succeeds for anything that exists, including
directories. libzip does not read the source until
`WritableZip::Finalize`, where reading a directory fails with EISDIR.
libzip then calls `zip_source_rollback_write`, which discards the entire
archive rather than just the offending entry.

`AddNetsimdLogs` triggers exactly that. It listed the netsimd temp
directory with a non-recursive `DirectoryContents`, so when netsimd runs
with `--pcap` it handed the `pcap/` directory straight to `AddFileAt`.
The capture files inside were never collected, and the directory entry
destroyed the rest of the bug report on its way out.

Require a regular file in `AddFileAt` so a bad entry can only lose
itself, and walk the netsimd tree so the captures are actually gathered.
The guard tests `S_ISREG` rather than just rejecting directories,
because a FIFO or a socket reaches `Finalize` the same way, and opening
one there can fail or block indefinitely.

`WalkDirectory` is not usable directly for that walk: it reports
directories to its callback, and an error returned from the callback
aborts the remaining walk. It also cannot be changed to skip
directories, because `SymlinkHostPackage`, `WriteFsConfig` and
`LinkOrCopyDirectoryContentsRecursively` all depend on receiving them.
Add a `WalkDirectoryFiles` sibling instead, which reports only
non-directories and declines to descend through symlinked directories so
a symlink loop cannot trap the walk.

The new `zip_file_test` cases fail against the unpatched library, the
archive one with `zip_close` reporting "Read error: Is a directory".

Test: bazel test //cuttlefish/host/libs/zip:zip_file_test //cuttlefish/common/libs/utils:files_test
Test: bazel build //cuttlefish/host/commands/host_bugreport:cvd_internal_host_bugreport
Bug: 562650640
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant