Skip to content

[Bug]: Access Violation in client when server raises exceptions in route handlers #117

Closed

Description

@macc2010

RAL Version:

1.0

IDE:

Delphi 10.4 Sydney

Operating System:

Windows

Bug Description

Bug Report: Access Violation in client when server raises exceptions in route handlers

Summary

When a route handler on the server raises an exception, the client receives an Access Violation instead of a valid HTTP error response. There are 3 related bugs causing this behavior.

Version

PascalRAL v0.12.2-1 beta

Problem Description

If a route handler (TRALRequest/TRALResponse) raises an exception and OnServerError is not assigned and RaiseError is false ( as default ),, the server does not send a valid HTTP response to the client. The client attempts to process an incomplete response and causes an Access Violation.

If I change RaiseError to True ( not default value ), the access violation dissapear.

Identified Bugs

Bug 1: Server does not send error response (RALSynopseServer.pas)

File: src/engine/synopse/RALSynopseServer.pas
Lines: 302-308

Current code:
except
on e: exception do
if Assigned(OnServerError) then
OnServerError(e)
else if RaiseError then
raise;
end;Problem: If OnServerError is not assigned and RaiseError = False, the exception is swallowed and no HTTP response is sent to the client.

Impact: The client waits for a response that never arrives.


Bug 2: Client does not check nil before accessing parameters (RALSynopseClient.pas)

File: src/engine/synopse/RALSynopseClient.pas
Line: 148

Current code:
AResponse.Params.AddParam('Stream', AResponse.ParamByName('ral_body').AsStream, rpkBODY);Problem: It does not check if ParamByName('ral_body') is nil before calling AsStream. If the server sends an error response without a body, this causes an Access Violation.

Impact: Access Violation on the client when the server responds without a body.


Bug 3: SetAsStream does not handle nil (RALParams.pas)

File: src/base/RALParams.pas
Lines: 419-428

Current code:
procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if FContent <> nil then
FreeAndNil(FContent);

AValue.Position := 0; // ← Access Violation if AValue is nil

FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end;Problem: It does not check if AValue is nil before accessing AValue.Position.

Impact: Access Violation when attempting to assign a nil stream.


Steps to Reproduce

  1. Create a route handler that raises an exception:scal
    procedure TFormServer.FServer_Handle_Route_Test(ARequest: TRALRequest; AResponse: TRALResponse);
    begin
    raise Exception.Create('Test error');
    end;2. Configure the server with RaiseError = False and without OnServerError:
    FServer.RaiseError := False;
    // FServer.OnServerError := nil; (by default)3. Call from the client:
    FClient.Post('api/test', nil);4. Result: Access Violation on the client at RALSynopseClient.pas line 148.

Expected Behavior

  • The server should send an HTTP 500 (Internal Server Error) response with a JSON error when an exception occurs.
  • The client should handle error responses without a body without causing an Access Violation.
  • SetAsStream should correctly handle the nil case.

Proposed Solution

Fix 1: RALSynopseServer.pas

except
on e: exception do
begin
if Assigned(OnServerError) then
OnServerError(e)
else if RaiseError then
raise
else
begin
// Send HTTP 500 error response to client
if vResponse <> nil then
begin
vResponse.StatusCode := HTTP_InternalError;
vResponse.ContentType := rctAPPLICATIONJSON;
vResponse.ResponseText := Format('{"Status":"ERROR","Message":"%s"}',
[StringReplace(e.Message, '"', '"', [rfReplaceAll])]);

    AContext.OutContent := vResponse.ResponseText;
    AContext.OutContentType := vResponse.ContentType;
    AContext.OutCustomHeaders := 'Content-Type: ' + string(vResponse.ContentType) + HTTPLineBreak;
    Result := HTTP_InternalError;
  end;
end;

end;
end

;### Fix 2: RALSynopseClient.pas
var vBodyParam := AResponse.ParamByName('ral_body');
if vBodyParam <> nil then
AResponse.Params.AddParam('Stream', vBodyParam.AsStream, rpkBODY);

Fix 3: RALParams.pas

procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if FContent <> nil then
FreeAndNil(FContent);

if AValue <> nil then
begin
AValue.Position := 0;
FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end
else
FContent := nil;
end;---

Environment

  • Delphi: D10
  • Engine: mORMot2 (Synopse)
  • OS: Windows 10

Priority

High — causes Access Violation and blocks production use.

Steps to reproduce the bug

In bug description

Expected behavior

No response

Pictures of the issue

No response

Referral

No response

Activity

  1. added
    ide: DelphiIssues that impact the Delphi IDE
    seg: ServerIssues that happen on the Server side of the package
    seg: ClientIssues that happen on the Client side of the package
    sev: CriticalThe degree on which the issue impacts functionality of the package
    os: WindowsIssues that happens on Windows
    sec: APIIssues that happen with the API feature of the package
    pri: HighHighest Priority for issues that are confirmed bugs
    rev: OngoingWhen a reported issue was able to be reproduced and is currently being worked on
    on Nov 17, 2025
  2. added theissue type on Nov 17, 2025
  3. added this to the 1.0 milestone on Nov 17, 2025
  4. mobius1qwe commented on Nov 17, 2025

    @mobius1qwe
    Contributor
  5. added a commit that references this issue on Nov 17, 2025
  6. mobius1qwe commented on Nov 17, 2025

    @mobius1qwe
    Contributor

    A Fix was just submitted to the Dev branch, could you please confirm that it solves the issue?

    Thanks in advance

  7. macc2010 commented on Nov 18, 2025

    @macc2010
    Author

    The problem is here :

    constructor TRALStringStream.Create(AStream: TStream);
    begin
    inherited Create;
    WriteStream(AStream);
    end;

    AStream comes with nil value, so I have changed by :

    constructor TRALStringStream.Create(AStream: TStream);
    begin
    inherited Create;
    if AStream <> nil then
    WriteStream(AStream);
    end;

    And the problem has been solved

    Thank you

  8. mobius1qwe commented on Nov 18, 2025

    @mobius1qwe
    Contributor

    Could you provide a sample that reproduces the issue? Zip it and attach to the issue here please

  9. macc2010 commented on Nov 18, 2025

    @macc2010
    Author

    Now I have not a project that I can upload, but the server is a vcl project ( not a console ), and the client is a vcl project, in server I am using TRALSynopseServer with TRALServerBasicAuth and in client a TRALClient with EngineType := 'mORMot2' and using FClient.Post ( synchronous ) and after that I use Response1.ResponseText to see the server response.

  10. macc2010 commented on Nov 18, 2025

    @macc2010
    Author

    I have seen that this code :

    procedure TRALParam.SetAsStream(const AValue: TStream);
    begin
    if FContent <> nil then
    FreeAndNil(FContent);
    if AValue <> nil then
    begin
    AValue.Position := 0;
    FContent := TRALStringStream.Create(AValue);
    FContent.Position := 0;
    end
    else
    FContent := nil; <----------------- memory leak when finish app if FContent had a value
    end;

    Should be this :

    procedure TRALParam.SetAsStream(const AValue: TStream);
    begin
    if FContent <> nil then
    FreeAndNil(FContent);
    if AValue <> nil then
    begin
    AValue.Position := 0;
    FContent := TRALStringStream.Create(AValue);
    FContent.Position := 0;
    end
    else
    FreeAndNil(FContent);
    end;

    Is not it?

  11. mobius1qwe commented on Nov 18, 2025

    @mobius1qwe
    Contributor

    That line is redundant, its just a fix to check if your AV stopped happening, the first thing the method does is verify if FContent is assigned or not, if it is, it gets destroyed and set to nil before checking if the AValue should be assigned to it.

    Keep in mind that all this checks and verifications are happening while a request is being sent to the server, so it has to be fast and precise, no room for redundancies as it will slow down the server a lot.

  12. macc2010 commented on Nov 18, 2025

    @macc2010
    Author

    I was in a mistaken, FContent is needed in more sites

    I think that the solution should to be this :

    procedure TRALParam.SetAsStream(const AValue: TStream);
    begin
    if AValue <> nil then
    AValue.Position := 0;
    FreeAndNil(FContent);
    FContent := TRALStringStream.Create(AValue);
    FContent.Position := 0;
    end;

    and this :

    constructor TRALStringStream.Create(AStream: TStream);
    begin
    inherited Create;
    if AStream <> nil then
    WriteStream(AStream);
    end;

  13. mobius1qwe commented on Nov 18, 2025

    @mobius1qwe
    Contributor

    I think it's easier if you make a simple sample that represents your issue and attach it here on the issue, that way I can see how the error happens and make a quick fix for it. You could grab the attached zip that I posted to test the issue and modify it to represent your environment, then you re-attach it here so it would be faster to find the issue and fix it

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

    ide: DelphiIssues that impact the Delphi IDEos: WindowsIssues that happens on Windowspri: HighHighest Priority for issues that are confirmed bugsrev: OngoingWhen a reported issue was able to be reproduced and is currently being worked onsec: APIIssues that happen with the API feature of the packageseg: ClientIssues that happen on the Client side of the packageseg: ServerIssues that happen on the Server side of the packagesev: CriticalThe degree on which the issue impacts functionality of the packagevcs: ErrorWhen the issue is a bug

    Type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions