ifcparse: check the result of fopen before using the FILE*

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.
This commit is contained in:
yekose
2026-07-31 10:30:47 +02:00
committed by Thomas Krijnen
parent 1f9a0a53bb
commit 9e6797e172
+14
View File
@@ -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_);