/******************************************************************************** * * * This file is part of IfcOpenShell. * * * * IfcOpenShell is free software: you can redistribute it and/or modify * * it under the terms of the Lesser GNU General Public License as published by * * the Free Software Foundation, either version 3.0 of the License, or * * (at your option) any later version. * * * * IfcOpenShell is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * Lesser GNU General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * * * ********************************************************************************/ // libFuzzer entry point for ifcopenshell::file. Parses the input entirely // in-memory (no subprocess, no temp files) so a coverage-guided fuzzer can // reach the tokenizer and argument parser directly instead of only ever // observing IfcConvert's exit code. #include "ifcparse/file.h" #include "ifcparse/logger.h" #include #include #include #include #include #include namespace { bool is_regular_file(const char* path) { struct stat st; return ::stat(path, &st) == 0 && S_ISREG(st.st_mode); } } // namespace // libFuzzer runs in two modes: a real fuzzing campaign (given corpus // directories to mutate from, executed millions of times) and a // single-input repro (given one or more explicit file paths, e.g. // `-runs=1 crashes//input`). logger::set_output is only wired up for // the latter -- logging every parse warning to a stream on every execution // of a real campaign would dominate the runtime. extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { for (int i = 1; i < *argc; ++i) { if (is_regular_file((*argv)[i])) { ifcopenshell::logger::root().set_output(&std::cerr, &std::cerr); break; } } return 0; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (size == 0 || size > static_cast(std::numeric_limits::max())) { return 0; } try { ifcopenshell::file ifc_file(const_cast(static_cast(data)), static_cast(size)); if (ifc_file.good()) { // Constructing the file already tokenizes and type-checks every // attribute of every instance (and resolves references), so // most tokenizer/argument bugs are reachable without going any // further. to_string() is still exercised here since // reserialization walks a different code path and may surface // additional faults. std::ostringstream discard; for (const auto& entity : ifc_file) { try { entity.second.to_string(discard); } catch (const std::exception&) { // Malformed attributes are expected on fuzzed input. } } } } catch (const std::exception&) { // IfcException (and friends) is expected control flow for malformed // input, not a bug. Only crashes caught by ASan/UBSan/libFuzzer // itself - which bypass try/catch - are findings. } return 0; }