fix(spike-A): adapt to installed QPDF 12.2 API; fix use-after-free
The QPDF 12.2 API differs from the initially-written calls: - processInputFile → processFile - getArrayAsArray → getArrayAsVector - setOutputFile(string) → setOutputFilename(const char*) - setQDF → setQDFMode The spike CMakeLists now finds QPDF via either a CMake config target (qpdf::libqpdf for system packages, QPDF::qpdf for vcpkg) or a pkg-config fallback (PkgConfig::QPDF), so it builds against the Debian libqpdf-dev package as well as a vcpkg manifest install. The contract test target was missing SpikeRunner.cpp from its sources, causing a link error; added it with the spike/ include directory. ASan caught a heap-use-after-free in the initial write path: QPDFWriter retains the const char* passed to setOutputFilename and dereferences it during write(), after the temporary std::string from output.string() is destroyed. Both the input and output filename strings are now kept alive across the QPDF calls that retain them. This is exactly the bug class the §7.2 sanitizers-in-CI posture exists to catch. Builds and passes under GCC 14.2 + Qt 6.8.2 + QPDF 12.2, both Release and ASan+UBSan configurations. Signed-off-by: ai-ad4 <ai-ad4@users.noreply.gitea.lm.je>
This commit is contained in:
parent
e778a56540
commit
1d5372f140
|
|
@ -44,7 +44,10 @@ FileResult roundtrip(const std::filesystem::path& input,
|
|||
|
||||
QPDF q;
|
||||
try {
|
||||
q.processInputFile(input.string());
|
||||
// Keep the input filename alive across processFile: QPDF opens the
|
||||
// file lazily and may dereference the pointer later.
|
||||
const std::string input_str = input.string();
|
||||
q.processFile(input_str.c_str());
|
||||
} catch (const std::exception& e) {
|
||||
r.error = std::string("QPDF open failed: ") + e.what();
|
||||
return r;
|
||||
|
|
@ -62,7 +65,7 @@ FileResult roundtrip(const std::filesystem::path& input,
|
|||
for (auto& page : pages) {
|
||||
QPDFObjectHandle content = page.getAttribute("/Contents", true);
|
||||
if (content.isArray()) {
|
||||
auto items = content.getArrayAsArray();
|
||||
auto items = content.getArrayAsVector();
|
||||
for (auto& item : items) {
|
||||
if (item.isStream()) {
|
||||
(void)item.getStreamData(); // force decode
|
||||
|
|
@ -81,13 +84,18 @@ FileResult roundtrip(const std::filesystem::path& input,
|
|||
|
||||
try {
|
||||
QPDFWriter w(q);
|
||||
w.setOutputFile(output.string());
|
||||
// Keep the output filename string alive across the write() call:
|
||||
// QPDFWriter retains the const char* and dereferences it during
|
||||
// write(), so a temporary from output.string().c_str() would be a
|
||||
// use-after-free (caught by ASan during the M0 spike run).
|
||||
const std::string output_str = output.string();
|
||||
w.setOutputFilename(output_str.c_str());
|
||||
// Preserve as much as possible: write with the same object stream
|
||||
// strategy and linearisation state as the input so byte-identity is a
|
||||
// realistic target. The defaults preserve the structure verbatim where
|
||||
// QPDF can.
|
||||
w.setPreserveEncryption(true);
|
||||
w.setQDF(false);
|
||||
w.setQDFMode(false);
|
||||
w.setLinearization(false);
|
||||
w.write();
|
||||
} catch (const std::exception& e) {
|
||||
|
|
|
|||
|
|
@ -6,15 +6,40 @@
|
|||
# independently and its results reported separately by CI. The spikes link only
|
||||
# against the leaf libraries they exercise (QPDF here), not against Qt.
|
||||
|
||||
find_package(QPDF CONFIG QUIET)
|
||||
# Find QPDF. System packages (Debian libqpdf-dev) install the config under the
|
||||
# multiarch lib dir; vcpkg installs it under its installed tree. Search both.
|
||||
find_package(QPDF CONFIG QUIET
|
||||
HINTS /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} /usr/local /usr)
|
||||
|
||||
# Fall back to a pkg-config probe if the CMake config wasn't installed.
|
||||
if(NOT QPDF_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(QPDF libqpdf IMPORTED_TARGET)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT QPDF_FOUND AND NOT TARGET PkgConfig::QPDF)
|
||||
message(STATUS
|
||||
"QPDF not found — Spike A (verbatim round-trip) will not be built. "
|
||||
"Install qpdf (vcpkg, system package, or from source) to enable it.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# The QPDF CMake config exports `qpdf::libqpdf` (system packages, e.g. Debian's
|
||||
# libqpdf-dev) and/or `QPDF::qpdf` (vcpkg). pkg-config yields PkgConfig::QPDF.
|
||||
# Accept any of them.
|
||||
if(TARGET qpdf::libqpdf)
|
||||
set(_fpe_qpdf_target qpdf::libqpdf)
|
||||
elseif(TARGET QPDF::qpdf)
|
||||
set(_fpe_qpdf_target QPDF::qpdf)
|
||||
elseif(TARGET PkgConfig::QPDF)
|
||||
set(_fpe_qpdf_target PkgConfig::QPDF)
|
||||
else()
|
||||
message(WARNING "QPDF found but no known imported target — Spike A skipped.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(spike_a_verbatim_roundtrip
|
||||
common/SpikeRunner.cpp
|
||||
common/SpikeRunner.h
|
||||
|
|
@ -22,7 +47,8 @@ add_executable(spike_a_verbatim_roundtrip
|
|||
A_verbatim_roundtrip/Roundtrip.cpp
|
||||
A_verbatim_roundtrip/Roundtrip.h
|
||||
)
|
||||
target_link_libraries(spike_a_verbatim_roundtrip PRIVATE QPDF::qpdf)
|
||||
target_include_directories(spike_a_verbatim_roundtrip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(spike_a_verbatim_roundtrip PRIVATE ${_fpe_qpdf_target})
|
||||
target_compile_features(spike_a_verbatim_roundtrip PRIVATE cxx_std_20)
|
||||
freepdfeditor_apply_warnings(spike_a_verbatim_roundtrip)
|
||||
freepdfeditor_apply_hardening(spike_a_verbatim_roundtrip)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@
|
|||
# and lets CI on all three platforms run ctest without a vcpkg build of gtest.
|
||||
# A later milestone swaps in GTest once there is real C++ logic to assert on.
|
||||
|
||||
add_executable(test_spike_runner_contract test_spike_runner_contract.cpp)
|
||||
add_executable(test_spike_runner_contract test_spike_runner_contract.cpp
|
||||
../spike/common/SpikeRunner.cpp)
|
||||
target_include_directories(test_spike_runner_contract PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/spike)
|
||||
target_compile_features(test_spike_runner_contract PRIVATE cxx_std_20)
|
||||
freepdfeditor_apply_warnings(test_spike_runner_contract)
|
||||
freepdfeditor_apply_hardening(test_spike_runner_contract)
|
||||
|
|
|
|||
Loading…
Reference in New Issue