#1 2026-03-22 13:16:11

vs
Member
Registered: 2019-10-21
Posts: 56

SessionsLoadFromFile breaks all session after restart

SessionsLoadFromFile breaks all session lookups due to fSessionCounterMin mismatch after restart

When using SessionsSaveToFile/SessionsLoadFromFile (or Shutdown with a state file) for session persistence across restarts, all session lookups via LockedSessionFind fail after the server restarts.

  Root cause:
  - In TRestServer.Create, fSessionCounterMin := Random32(1 shl 20) + 10 (a new random value on every startup, up to ~1,048,585)
  - SessionsLoadFromFile restores fSessionCounter from the file, but fSessionCounterMin stays at the new random value
  - LockedSessionFind checks: if (aSessionID <= fSessionCounterMin) or (aSessionID > cardinal(fSessionCounter)) then exit;
  - For any small deployment where saved fSessionCounter < new fSessionCounterMin, ALL sessions (loaded and newly created) are silently rejected

  This makes SessionsSaveToFile/SessionsLoadFromFile effectively broken for any server with fewer than ~1 million total logins (which is most deployments).

  Proposed fix in SessionsLoadFromFile, after restoring fSessionCounter:
      fSessionCounter := PCardinal(R.P)^;
      fSessionCounterMin := 0; // allow all loaded session IDs to pass bounds check

  Setting fSessionCounterMin=0 is safe because session IDs 0 and 1 (CONST_AUTHENTICATION_NOT_USED/NOT_STARTED) are rejected by the caller before LockedSessionFind is ever reached.

  Regression from mORMot1: the old TSQLRestServerDB.Shutdown(filename) + SessionsLoadFromFile pattern worked reliably in mORMot1 because fSessionCounterMin was not randomized.

Offline

#2 2026-03-22 13:45:09

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,429
Website

Re: SessionsLoadFromFile breaks all session after restart

Nice finding.

The regression tests were indeed not enough to identify it, because it did reuse the same fSessionCounterMin.

Should be fixed by https://github.com/synopse/mORMot2/commit/b323ab692

Offline

#3 2026-03-22 14:11:19

vs
Member
Registered: 2019-10-21
Posts: 56

Re: SessionsLoadFromFile breaks all session after restart

Arnaud, you're amazing as always.

I barely finished my coffee before we received the fix.

My sincere respect and gratitude!

Added...
Checked, fix works!

Last edited by vs (2026-03-22 14:24:39)

Offline

#4 2026-03-22 22:01:42

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,429
Website

Re: SessionsLoadFromFile breaks all session after restart

You made a proper investigation, so it was easy to fix.

Thanks to you!

Offline

#5 2026-03-26 09:24:02

vs
Member
Registered: 2019-10-21
Posts: 56

Re: SessionsLoadFromFile breaks all session after restart

Hi Arnaud, back in this thread.

Another issue found on another production implementation.

SessionsSaveToFile/LoadFromFile truncates 64-bit User.IDValue to 32-bit

TAuthSession.SaveTo and CreateFrom use WriteVarUInt32/VarUInt32 for fUser.IDValue and fUser.GroupRights.IDValue:

// SaveTo (line 5066-5068):
W.WriteVarUInt32(fUser.IDValue);
W.WriteVarUInt32(fUser.GroupRights.IDValue);

// CreateFrom (line 5083, 5086):
fUser.IDValue := Read.VarUInt32;
fUser.GroupRights.IDValue := Read.VarUInt32;

IDValue is TID = Int64, but VarUInt32 handles only 32-bit values. When using TSynUniqueIdentifierGenerator (63-bit IDs), the upper
bits are silently lost.

Reproduction:
1. Use TSynUniqueIdentifierGenerator for TAuthUser/TAuthGroup tables
2. Login → session created with 63-bit User.ID (e.g. 3810583231573688321)
3. Stop server → SessionsSaveToFile truncates to low 32 bits (2164424705)
4. Start server → SessionsLoadFromFile restores truncated ID → user not found in DB → session is useless

Fix: replace WriteVarUInt32/VarUInt32 with WriteVarUInt64/VarUInt64 for both IDValue fields. This changes the .ses binary format
(old files would need to be discarded on upgrade).

Offline

#6 2026-03-26 21:06:43

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,429
Website

Re: SessionsLoadFromFile breaks all session after restart

In fact, such 64-bit IDs were never supported because they could not be encoded as pointers on 32-bit apps.
If you use published properties of type TOrm, the ID should fit into a PtrInt, which is only 32-bit on 32-bit apps.

So it was by design.

Offline

#7 Yesterday 11:25:23

vs
Member
Registered: 2019-10-21
Posts: 56

Re: SessionsLoadFromFile breaks all session after restart

Thank you Arnaud for the explanation.

That makes perfect sense — GroupRights stored as pointer(IDValue) limits IDs to PtrInt size.

We were assigning TSynUniqueIdentifierGenerator to all tables including TAuthUser/TAuthGroup, which produced 63-bit IDs
incompatible with this design.

Our fix: skip auth tables in SetIDGenerator loop:
if vClass.InheritsFrom(TAuthUser) or vClass.InheritsFrom(TAuthGroup) then
  continue;

Auth tables now use default auto-increment, domain tables keep TSynUniqueIdentifier. Problem solved.

Perhaps worth a note in the SetIDGenerator documentation that it should not be used with TAuthUser/TAuthGroup descendants?

Offline

Board footer

Powered by FluxBB