Skip to content

fcl-base: blowfish2: AV error in BlowFish2 base class destructor

Summary

A destructor for TBlowFish2Stream, which is a base stream for both encryption and decryption stream, contains an error causing a memory protection error - making it impossible to propertly dispose either of the streams.

System Information

  • Operating system: Any
  • Processor architecture: Any
  • Compiler version: Free Pascal Compiler version 3.3.1-17647-gddd846ede4 [2025/03/16] for x86_64
  • Device: Any

Steps to reproduce

Just instantiate and then try to free a TBlowFish2EncryptStream or TBlowFish2DeCryptStream instance.

Example Project

program BlowfishBug;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, BlowFish2;

function EncryptString(const AKey: UTF8String; const AString: String): TBytes;
var
  EncryptStream: TBlowFish2EncryptStream;
  DestStream: TBytesStream;
  EncryptedLength: Integer;
begin
  DestStream := TBytesStream.Create;
  DestStream.Size := 1024 * 8;

  EncryptStream := TBlowFish2EncryptStream.Create(AKey, DestStream);
  EncryptStream.WriteAnsiString(AString);

  { AV here - same for Destroy or FreeAndNil }
  EncryptStream.Free;

  Result := DestStream.Bytes;
  EncryptedLength := DestStream.Position;
  DestStream.Free;

  SetLength(Result, EncryptedLength);
end;

function DecryptString(const AKey: UTF8String; ABytes: TBytes): String;
var
  DecryptStream: TBlowFish2DeCryptStream;
  SourceStream: TBytesStream;
begin
  SourceStream := TBytesStream.Create(ABytes);
  DecryptStream := TBlowFish2DeCryptStream.Create(AKey, SourceStream);
  Result := DecryptStream.ReadAnsiString;

  { AV here - same for Destroy or FreeAndNil }
  DecryptStream.Free;

  SourceStream.Free;
end;

const
  Key = 'My Secret Key';

var
  InStr, OutStr: String;
  Encrypted: TBytes;

begin
  InStr := 'Free Pascal is a mature, versatile, open source Pascal compiler.';
  Writeln('In : ', InStr);

  Encrypted := EncryptString(Key, InStr);
  Writeln('Encrypted length: ', Length(Encrypted));

  OutStr := DecryptString(Key, Encrypted);
  Writeln('Out: ', OutStr);
end.

What is the current bug behavior?

Calling the stream destructor results in Access Violation.

What is the expected (correct) behavior?

The stream is disposed properly.

Possible fixes

Offending line here https://gitlab.com/freepascal.org/fpc/source/-/blob/ddd846ede494e86e85832d1bb3a1043c4196bea2/packages/fcl-base/src/blowfish2.pp#L1050

The destructor calls FreeAndNil for FBF property which is a mistake, because FBF is not a TObject descendant but a TBlowfish2Context record instance. Therefore FreeAndNil needs to be removed, then since it was the only operation in the destructor - the destructor itself is no longer needed.

Patch blowfish2.pp.patch

Merge request !974 (merged)

Edited by Oleg Latov
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information