Skip to content

GCPnts_UniformAbscissa/QuasiUniformAbscissa: two unpatched kernel defects behind #501's buffer-overflow fix (unbounded NbPoints(), degenerate-count SIGSEGV) #555

Description

@gsdali

#501/#544 fixed a heap buffer overflow and an uncatchable SIGSEGV reachable through four bridge functions, by guarding the bridge layer (occtSamplerKept/occtSamplerIndex/occtValidSampleCount in OCCTBridge_Internal.h). That fix is bridge-only: no Scripts/patches/*.patch was carried, OCCT.xcframework was not rebuilt, and neither defect is filed upstream against Open-Cascade-SAS/OCCT. The two underlying kernel bugs still exist in the pinned source (verified directly in Libraries/occt-src) — this issue tracks patching and filing them properly, following this project's usual practice for confirmed kernel-level memory-safety defects (e.g. #310, #317, #318, #341).

Defect 1: GCPnts_UniformAbscissa::NbPoints() is not bounded by the requested count

GCPnts_UniformAbscissa::initialize (ModelingData/TKGeomBase/GCPnts/GCPnts_UniformAbscissa.cxx:515) sizes its internal parameter array at theNbPoints + 5:

const int aSize = theNbPoints + 5;
...
myParams = new NCollection_HArray1<double>(1, aSize);

but the arc-length walk (Perform/PerformLengthParametrized, same file) fills the array as far as it runs and sets myNbPoints = anIndex (:155, :217) with no clamp against the original theNbPoints. NbPoints() (GCPnts_UniformAbscissa.hxx:216-219) just returns myNbPoints directly — so any caller that sizes its own output buffer from the requested count rather than the actual NbPoints() after construction can be handed more points than it asked for, bounded only by the +5 padding.

GCPnts_QuasiUniformAbscissa::initialize (GCPnts_QuasiUniformAbscissa.cxx:117-120) inherits this for every curve that is not Bezier/BSpline, since it forwards straight to GCPnts_UniformAbscissa:

if (theC.GetType() != GeomAbs_BezierCurve && theC.GetType() != GeomAbs_BSplineCurve)
{
  GCPnts_UniformAbscissa aUA(theC, theNbPoints, theU1, theU2);
  myNbPoints = aUA.NbPoints();
  ...
}

Reproduced: on an ellipse with major radius 1e6 and minor radius 1e-3, the arc-length walk lands roughly 1.6e-8 short of the end parameter — far outside the sampler's own local epsilon (Resolution(1e-7), about 1e-13 there) — so it takes one extra step and snaps that step to the true end parameter. 22 of the first 59 requested point counts overshoot the request by exactly one. Full measured tables at Scripts/repro/501-quasiuniform-buffer-overflow/.

This is a real footgun for any OCCT consumer (not just OCCTSwift) that follows the documented contract of "allocate theNbPoints slots, call Parameter(i) for i in 1...theNbPoints" — the class does not actually guarantee that bound.

Defect 2: degenerate point count SIGSEGVs the Bezier/BSpline branch

GCPnts_QuasiUniformAbscissa::initialize's Bezier/BSpline branch (GCPnts_QuasiUniformAbscissa.cxx:128-158) guards theNbPoints <= 1 with:

Standard_ConstructionError_Raise_if(
    theNbPoints <= 1,
    "GCPnts_QuasiUniformAbscissa::Initialize(), number of points should be >= 2");

*_Raise_if macros compile to nothing under No_Exception, which is how this project's pinned Release OCCT.xcframework is built (see #487). With that guard neutralized and theNbPoints == 0:

  • aLP is constructed as NCollection_Array1<gp_Pnt2d>(1, 2*theNbPoints) = (1, 0), an empty range — harmless on its own, since the fill loop (for (i = 0; i < 2*theNbPoints; ++i)) never executes.
  • aLength stays 0.0, so the IsEqual(aLength, 0.0) branch is taken.
  • myParams = new NCollection_HArray1<double>(1, theNbPoints) = (1, 0), also an empty range.
  • The very next line, unconditional: myParams->SetValue(1, theU1); — writes index 1 into an array whose valid range is empty. Uncatchable out-of-bounds store; SIGSEGVs.

Reproduced on a 4-pole Bezier, an all-coincident-pole Bezier, and an 8-point BSpline fit, all with nbPoints = 0. (On non-Bezier/BSpline curves — a line, circle, ellipse — nbPoints = 0 does not crash; it silently returns IsDone() with 5 points, a separate but less severe misbehavior of the same missing precondition.)

Status

Suggested fix

Following the pattern already carried in Scripts/patches/:

  • Defect 1: clamp myNbPoints (or anIndex) to theNbPoints in GCPnts_UniformAbscissa::Perform/PerformLengthParametrized, or document/enforce the true upper bound (theNbPoints + 5) so callers can size correctly instead of being surprised.
  • Defect 2: an explicit, always-enforced range check before SetValue(1, theU1) in the Bezier/BSpline branch of GCPnts_QuasiUniformAbscissa::initialize — not reliant on Raise_if compiling in.

Carry both as a kernel patch (Scripts/patches/0018-* or next available number), rebuild OCCT.xcframework, and file upstream as a repro + fix PR, matching the project's established practice for confirmed kernel-level memory-safety defects.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cluster:kernelOCCTSwift core geometry/meshing/IO librariesphase:1bPass 1b — C++ bridge header duplication audit (#381)priority:P2Normaltype:bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions