Files
fpc-msgbase/tests/adversarial/test_fuzz_pcboard.pas
Ken Johnson 94dcd27005 Add corruption-resilience fuzz tests
New tests/adversarial/ suite covers each driver plus mb.kludge with
crafted-input scenarios: empty files, truncated headers, garbage
payloads, oversized length fields, infinite-loop bait.  The
invariant under test is graceful degradation: no crash, no hang,
no OOM.  Every allocation caps, every loop terminates, every
unreadable record returns False cleanly.

Coverage:
  test_fuzz_jam       7 cases  (.JHR/.JDT/.JDX/.JLR corruption)
  test_fuzz_squish    5 cases  (clen underflow, 2 GB clen, garbage idx)
  test_fuzz_hudson    3 cases  (bundle-file corruption)
  test_fuzz_goldbase  2 cases
  test_fuzz_pcboard   2 cases
  test_fuzz_msg       4 cases  (50 MB no-NUL body, strange names)
  test_fuzz_kludge    3 cases  (100 K CRs, 1 M CRs, legit round-trip)

run_tests.sh builds and runs them after the happy-path suite.
All 26 fuzz cases pass; all 47 existing tests still pass.
2026-04-19 06:44:34 -07:00

103 lines
2.5 KiB
ObjectPascal

{
test_fuzz_pcboard.pas - corruption-resilience for the PCBoard driver.
PCBoard is a single .MSG file (e.g. ECHO.MSG) plus .NDX index.
Test IDs:
F-PC-1 empty .MSG / .NDX
F-PC-2 truncated .MSG
F-PC-3 garbage .MSG -- ReadMessage bounded
}
program test_fuzz_pcboard;
{$mode objfpc}{$H+}
uses
Classes, SysUtils,
testutil,
mb.types, mb.api,
mb.fmt.pcboard, mb.fmt.pcboard.uni;
const
SCRATCH = '/tmp/mb_fuzz_pcboard';
procedure FreshDir; begin ForceDirectories(SCRATCH); end;
procedure WriteBytes(const APath: string; const B: array of byte);
var fs: TFileStream;
begin
fs := TFileStream.Create(APath, fmCreate);
try if Length(B) > 0 then fs.Write(B[0], Length(B)); finally fs.Free; end;
end;
function SafeOpen(out Base: TMessageBase; const ABaseName: string): boolean;
begin
Result := False; Base := nil;
try
Base := MessageBaseOpen(mbfPCBoard, ABaseName, momReadOnly);
Result := Base.Open;
except
on E: Exception do begin
TestFail('Open raised: ' + E.ClassName + ': ' + E.Message);
if Assigned(Base) then begin Base.Free; Base := nil; end;
end;
end;
end;
procedure CloseAndFree(var Base: TMessageBase);
begin
if Base = nil then exit;
try Base.Close; except end;
try Base.Free; except end;
Base := nil;
end;
{ ============================================================ }
procedure TestEmpty;
var base: TMessageBase; p: string;
begin
TestBegin('F-PC-1: empty .MSG / .NDX -> graceful');
FreshDir; p := SCRATCH + '/ECHO';
WriteBytes(p + '.MSG', []);
WriteBytes(p + '.NDX', []);
if not SafeOpen(base, p) then begin TestOK; exit; end;
try AssertTrue('MessageCount >= 0', base.MessageCount >= 0);
finally CloseAndFree(base); end;
TestOK;
end;
procedure TestGarbage;
var
base: TMessageBase;
p: string;
garb: array[0..4095] of byte;
i: integer;
msg: TUniMessage;
n: longint;
begin
TestBegin('F-PC-3: garbage .MSG/.NDX -> ReadMessage bounded, no crash');
FreshDir; p := SCRATCH + '/ECHO';
for i := 0 to High(garb) do garb[i] := byte(($1D + i) and $FF);
WriteBytes(p + '.MSG', garb);
WriteBytes(p + '.NDX', garb);
if not SafeOpen(base, p) then begin TestOK; exit; end;
try
n := base.MessageCount;
AssertTrue('MessageCount bounded',
(n >= 0) and (n <= Length(garb)));
for i := 0 to 7 do
if i < n then base.ReadMessage(i, msg);
finally CloseAndFree(base); end;
TestOK;
end;
begin
WriteLn('fpc-msgbase: PCBoard corruption-resilience fuzz');
WriteLn;
TestEmpty;
TestGarbage;
Halt(TestsSummary);
end.