Compare commits

..

1 Commits

Author SHA1 Message Date
Bruno Postle 1156a5512b IfcParse: don't let a malformed binary token abort the parser
dispatch_token's Token_BINARY branch called TokenFunc::asBinary()
unguarded. A fuzzed file with a stray double-quote inside a numeric
list turns the next token into a 1-character Token_BINARY (e.g. a
lone '"'), which asBinary() rejects by throwing IfcException("Token
is not a valid binary sequence"). Nothing caught it, so it propagated
out of parse_context::construct and aborted the process instead of
producing a validation error.

Wrap the call in the same try/catch pattern already used a few lines
below for Token_ENUMERATION, logging a VAL error instead of crashing.
2026-07-16 22:41:03 +01:00
4 changed files with 26 additions and 30 deletions
+6 -19
View File
@@ -27,7 +27,6 @@ import ifcopenshell.api.material
import ifcopenshell.api.pset
import ifcopenshell.api.root
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.schema
import ifcopenshell.util.shape_builder
import ifcopenshell.util.type
@@ -130,25 +129,13 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
same_ifc_product = element.is_a(ifc_product)
if not same_ifc_product:
# A spatial element (e.g. IfcSite) anchors the containment
# hierarchy, so only allow reassigning it to another family when
# it actually carries geometry - i.e. it's a real modelled thing
# (a bench dropped onto IfcSite -> IfcFurniture) rather than an
# empty spatial container we'd be turning into a loose element.
# IfcSpatialStructureElement covers IFC2X3, which has no
# IfcSpatialElement supertype.
is_spatial = element.is_a("IfcSpatialElement") or element.is_a("IfcSpatialStructureElement")
if is_spatial:
has_geometry = (
next(ifcopenshell.util.representation.get_representations_iter(element), None) is not None
if not (element.is_a("IfcElement") and ifc_product == "IfcElementType") and not (
element.is_a("IfcElementType") and ifc_product == "IfcElement"
):
self.report(
{"ERROR"}, f"Not supported class reassignment for object '{obj.name}' -> {ifc_product}."
)
if not has_geometry:
self.report(
{"ERROR"},
f"Cannot reassign '{obj.name}' ({element.is_a()}) to {ifc_product}: "
"a spatial element can only be reassigned to another class when it has geometry.",
)
return {"CANCELLED"}
return {"CANCELLED"}
props = tool.Blender.get_object_bim_props(obj)
props.is_reassigning_class = False
+14 -3
View File
@@ -23,6 +23,7 @@ import numbers
import os
import re
import time
import types
import weakref
import zipfile
from collections.abc import Callable, Generator
@@ -243,14 +244,19 @@ file_dict: dict[int, tuple[weakref.ReferenceType[file], int]] = {}
"""Mapping of internal IfcFile pointer address to existing ``ifcopenshell.file``
and the timestamp when it was created.
Needed only to quickly access from ``entity_instance`` its ``file``.
Needed only to quickly access related from ``entity_instance`` it's ``file``.
"""
READ_ERROR = ifcopenshell_wrapper.file_open_status.READ_ERROR
NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER
UNSUPPORTED_SCHEMA = ifcopenshell_wrapper.file_open_status.UNSUPPORTED_SCHEMA
INVALID_SYNTAX = ifcopenshell_wrapper.file_open_status.INVALID_SYNTAX
UNKNOWN = ifcopenshell_wrapper.file_open_status.UNKNOWN
# TODO: Workaround for old builds, remove after build stabilizes.
try:
UNKNOWN = ifcopenshell_wrapper.file_open_status.UNKNOWN
except:
UNKNOWN = 5 # Workaround
import struct
@@ -1060,8 +1066,13 @@ class file:
@property
def header(self) -> file_header:
# TODO: Workaround for old builds, remove after build stabilizes.
# TODO: No need for `wrapped_data.header` to be a method - should use `@property`?
return file_header(self, self.wrapped_data.header())
header = self.wrapped_data.header
if isinstance(header, types.MethodType):
return file_header(self, self.wrapped_data.header())
else:
return self.wrapped_data.header
@property
def storage(self) -> Optional[rocksdb_file_storage]:
+5 -1
View File
@@ -61,7 +61,11 @@ namespace {
template <typename Fn>
void dispatch_token(boost::optional<size_t> instance_id, int attribute_id, IfcParse::Token t, IfcParse::declaration* decl, Logger& logger, Fn fn) {
if (t.type == IfcParse::Token_BINARY) {
fn(IfcParse::TokenFunc::asBinary(t));
try {
fn(IfcParse::TokenFunc::asBinary(t));
} catch (IfcParse::IfcException& e) {
logger.Error("VAL", 20, "Invalid binary token at offset " + std::to_string(t.startPos));
}
} else if (IfcParse::TokenFunc::isBool(t)) {
fn(IfcParse::TokenFunc::asBool(t));
} else if (IfcParse::TokenFunc::isLogical(t)) {
+1 -7
View File
@@ -35,13 +35,7 @@ namespace {
parse_context pc;
storage->tokens->Next();
storage->load(-1, nullptr, pc, -1);
// references_to_resolve is unset while reading the header (header
// entities such as FILE_DESCRIPTION never reference other
// instances), so fall back to a throwaway list instead of
// dereferencing a null pointer.
unresolved_references no_references;
unresolved_references& references = storage->references_to_resolve ? *storage->references_to_resolve : no_references;
return pc.construct(boost::none, references, decl, decl->as_entity()->attribute_count(), -1, logger);
return pc.construct(boost::none, *storage->references_to_resolve, decl, decl->as_entity()->attribute_count(), -1, logger);
} else {
// std::unreachable();
return IfcEntityInstanceData(in_memory_attribute_storage(10));