From 9e6797e1726d80a51b09d4e5a1c6c64b6efbb36d Mon Sep 17 00:00:00 2001 From: yekose Date: Fri, 31 Jul 2026 10:30:47 +0200 Subject: [PATCH] ifcparse: check the result of fopen before using the FILE* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FullBufferImpl and PagedFileImpl both open the file and then use the handle without ever testing it: auto stream = _wfopen(fn_wide, L"rb"); // null when the file is missing fseek(stream, 0, SEEK_END); // null goes straight to the CRT buf_.resize((size_t)ftell(stream)); Opening a path that does not exist therefore hands a null FILE* to the CRT. On MSVC that does not return an error: the runtime terminates the process immediately (fastfail, exit code 0xC0000409). No exception is thrown, no stack unwinding starts, so a caller cannot defend with try/catch — the host application simply dies. On glibc it is undefined behaviour as well. This is reachable through the ordinary entry point, because guess_file_type() answers FT_IFCSPF for a path that does not exist (its own comment calls this "just weird, but for consistency with earlier behaviour"), so a missing path flows into the reader rather than being reported. The fix is to leave the reader empty when the open fails. Both implementations then behave like a zero-length file: size() is 0 and get() throws out_of_range for any position, so the parse fails and IfcFile::good() reports it, which is what a caller can actually handle. PagedFileImpl's destructor already tested fp_ for null, so the possibility was known — only the constructor did not check. Verified by reading a non-existent path through IfcParse::IfcFile: the constructor returns and good() reports the failure, where before the process died with 0xC0000409 and no output. --- src/ifcparse/FileReader.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ifcparse/FileReader.cpp b/src/ifcparse/FileReader.cpp index e16d26e641..6d719b2e70 100644 --- a/src/ifcparse/FileReader.cpp +++ b/src/ifcparse/FileReader.cpp @@ -46,6 +46,13 @@ struct FullBufferImpl final : FileReader::Impl { #else auto stream = fopen(fn.c_str(), "rb"); #endif + if (stream == nullptr) { + // Missing or unreadable file. Leave the buffer empty so the + // caller sees a zero-length input and reports a read error; + // handing a null FILE* to the CRT below terminates the whole + // process instead of failing the parse. + return; + } fseek(stream, 0, SEEK_END); buf_.resize((size_t)ftell(stream)); rewind(stream); @@ -84,6 +91,13 @@ struct PagedFileImpl final : FileReader::Impl { #else fp_ = fopen(fn.c_str(), "rb"); #endif + if (fp_ == nullptr) { + // As above: behave like an empty file rather than passing a + // null FILE* to fseek. get() then throws out_of_range for any + // position and fetchPage_() is never reached. + file_size_ = 0; + return; + } fseek(fp_, 0, SEEK_END); file_size_ = (size_t)ftell(fp_); rewind(fp_);