Do not let a remote URL name a download's directory, or clobber it on… - #15084
nicholasaleks wants to merge 1 commit into
Conversation
Build ResultsPlatform Status
All builds passed. Pre-commit
Pre-commit hooks: 2 passed, 77 failed, 7 skipped. Test Resultslinux-coverage-integration: 54 passed, 0 skipped Code CoverageCoverage: 71.4% No baseline available for comparison Artifact Sizes
Updated: 2026-09-12 03:01:20 UTC • Commit: dfc2ef3 • Triggered by: MacOS |
cb7ceb6 to
dfc2ef3
Compare
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (11.11%) is below the target coverage (30.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## master #15084 +/- ##
===========================================
+ Coverage 25.47% 38.90% +13.43%
===========================================
Files 769 1087 +318
Lines 65912 98413 +32501
Branches 30495 44722 +14227
===========================================
+ Hits 16788 38291 +21503
+ Misses 37285 36379 -906
- Partials 11839 23743 +11904
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 841 files with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
… failure
QGCFileDownload derived the output file name from the remote URL with
QUrl::fileName(). That returns the last path segment *percent-decoded*, and the
decode happens before the split on '/', so a '%5C' arrives as a literal '\' and
is a directory separator on Windows: the write escapes TempLocation entirely.
Applying QFileInfo::fileName() reduces it to a plain file name, stripping
whatever the host treats as a separator.
The destination was also opened WriteOnly|Truncate before the request was even
issued, so a download that 404s, times out, or never reaches a server still
destroyed whatever was already at that path. Switching the member to QSaveFile
defers that: it writes to a temporary and only replaces the destination on
commit(), which now happens once the reply is known good. An uncommitted
QSaveFile discards its temporary when destroyed, so the failure paths need
nothing beyond the cleanup they already do. QGCFileHelper::atomicWrite() uses
the same class for the same reason.
The URLs reaching this code are not trusted: COMPONENT_METADATA.uri,
COMPONENT_INFORMATION.general_metadata_uri, the translationUri inside a
component-metadata document, and CAMERA_INFORMATION.cam_definition_uri all come
straight off an unauthenticated MAVLink link. Two smaller changes follow from
that:
- RequestMetaDataTypeStateMachine::_requestFile() and
ComponentInformationTranslation::downloadAndTranslate() now accept only the
schemes they actually fetch. QGCFileDownload treats an unrecognised scheme,
and any bare string, as a local file path.
- VehicleCameraControl::_httpRequest() drops QSslSocket::VerifyNone and uses
NoLessSafeRedirectPolicy, so a camera-definition URI cannot be silently
substituted by an on-path attacker.
Adds two tests: a hostile-name corpus that must resolve inside the download
directory, and an existing file that must survive a download that never
succeeds. The second fails on the parent commit.
dfc2ef3 to
55b044e
Compare
Description
Related to: https://github.com/nicholasaleks/infected-drones/tree/main/QGC-02_component_metadata_uri_ssrf
QGCFileDownload derived the output file name from the remote URL with QUrl::fileName(). That returns the last path segment percent-decoded, and the decode happens before the split on '/', so a '%5C' arrives as a literal '' and is a directory separator on Windows: the write escapes TempLocation entirely. Applying QFileInfo::fileName() reduces it to a plain file name, stripping whatever the host treats as a separator.
The destination was also opened WriteOnly|Truncate before the request was even issued, so a download that 404s, times out, or never reaches a server still destroyed whatever was already at that path. Switching the member to QSaveFile defers that: it writes to a temporary and only replaces the destination on commit(), which now happens once the reply is known good. An uncommitted QSaveFile discards its temporary when destroyed, so the failure paths need nothing beyond the cleanup they already do. QGCFileHelper::atomicWrite() uses the same class for the same reason.
The URLs reaching this code are not trusted: COMPONENT_METADATA.uri, COMPONENT_INFORMATION.general_metadata_uri, the translationUri inside a component-metadata document, and CAMERA_INFORMATION.cam_definition_uri all come straight off an unauthenticated MAVLink link. Two smaller changes follow from that:
Adds two tests: a hostile-name corpus that must resolve inside the download directory, and an existing file that must survive a download that never succeeds. The second fails on the parent commit.
Type of Change
Testing
Platforms Tested
Flight Stacks Tested
Screenshots
See video in: https://github.com/nicholasaleks/infected-drones/tree/main/QGC-02_component_metadata_uri_ssrf
Checklist
Related Issues
Related to: https://github.com/nicholasaleks/infected-drones/tree/main/QGC-02_component_metadata_uri_ssrf
By submitting this pull request, I confirm that my contribution is made under the terms of the project's dual license (Apache 2.0 and GPL v3).