Skip to content

Several fstapi correctness and memory-safety bugs #20

Description

@Rodrigodd

Hi. While I was working on fixing some bugs in a Rust implementation of an FST writer with the help of a fuzzer (see ekiwi/fst-writer#7), I found one or two bugs in libfst itself. When I started working on fixing those bugs, with the help of AI, adding coverage tests, etc., I ended up finding a lot more, which I listed below.

I am still working on it, but I am planning to start splitting the fixes into multiple PRs and submit them slowly as I review and clean them up. Before submitting 10+ PRs, though, I thought it would be good to ask the maintainers whether you are fine with that, or would prefer I do something different.

My work in progress is currently in this branch: https://github.com/Rodrigodd/libfst/tree/fix-writer-and-reader-bugs

Below is a list of the bugs I found and fixed so far. Each commit in the branch has a longer (although AI-generated) description of the bug and of the fix applied, and almost all of them come with a regression test that reproduces the bug.


Writer bugs:

  • Flush with nothing pending appends to the current time chain, shifting every later timestamp in the section.
  • A section that never receives a value change leaves a stray zero-length header, making the rest of the file unreachable.
  • A value change emitted before the first time change is written only to the frame and never reported by fstReaderIterBlocks2().
  • A variable-length value change emitted before the first time change is silently dropped.
  • Variable-length value changes keep growing the pending buffer after the dump size limit is reached.
  • fstWriterEmitValueChangeVec64() truncates each word to 32 bits, corrupting any value wider than 64 bits.
  • fstWriterEmitValueChangeVec32/64() read one word past the end when the width is a whole number of words.
  • Handle zero passes the bounds check in the value-change emitters and underflows into an out-of-bounds index.

Reader bugs:

  • fstReaderProcessHier() reallocates the facility process mask from scratch, discarding every mask bit the caller set.
  • A file whose hierarchy cannot be read returns a context with a NULL process mask that crashes on first use.
  • A file with no value change section never rewinds its hierarchy stream, so it reports an empty hierarchy.
  • fstReaderSetFacProcessMaskAll()/fstReaderClrFacProcessMaskAll() memset through a NULL mask.
  • Reals reconstructed from a section frame are missing the r prefix that the value-change path emits.
  • Compressed value change chains are decompressed with a length that includes the chain's own length varint (heap overflow with FASTLZ).
  • The section peek in fstReaderGetValueFromHandleAtTime() seeks one byte into the current section instead of the next one, and doesn't restore state.

Supporting work (not fixes, but part of the branch):

  • fastlz kept on its portable path, to avoid unaligned-access UB reports.
  • Regression tests for each fix, plus API coverage tests (fstapi.c line coverage 43.9% → 68.8%).
  • A clang source-based coverage build (-Dllvm_coverage=true, coverage-json target).
  • An ASan/UBSan CI job.
  • A round-trip libFuzzer target that models the writer and checks every reader entry point against it.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions