Contact Details
lxd_dong@bupt.edu.cn
Version
V5.9.1
Description
ticket_lifetime = 0 and Immediate Discard Semantics
Problem Summary
This issue concerns the TLS 1.3 NewSessionTicket.ticket_lifetime field when its value is 0.
The relevant rule is:
- Variable:
ticket_lifetime
- Action:
indicates immediate discard
- Condition:
ticket_lifetime is zero
The core question is whether wolfSSL discards such a ticket immediately, as required by the TLS 1.3 specification, or whether it still stores the ticket and only rejects it later during session resumption.
RFC 8446 Original Text
RFC 8446 Section 4.6.1, "New Session Ticket Message":
https://www.rfc-editor.org/rfc/rfc8446.html#section-4.6.1
Relevant text:
ticket_lifetime: Indicates the lifetime in seconds as a 32-bit
unsigned integer in network byte order. The value of zero
indicates that the ticket should be discarded immediately.
Additional surrounding text from the same section:
Clients MUST NOT cache tickets for longer than 7 days, regardless
of the ticket_lifetime, and MAY delete tickets earlier based on
local policy. A server MAY treat a ticket as valid for a shorter
period of time than what is stated in the ticket_lifetime.
The important point here is that the specification does not say "reject later if used".
It says the ticket should be discarded immediately.
wolfSSL Code Behavior
1. Ticket is stored first
When a TLS 1.3 NewSessionTicket is received, wolfSSL parses the ticket and stores it into the session object:
src/tls13.c:12201 calls SetTicket(...)
src/tls13.c:12209 sets ssl->timeout = lifetime
src/tls13.c:12210 sets ssl->session->timeout = lifetime
src/tls13.c:12212 sets ssl->session->ticketSeen = now
src/tls13.c:12250 calls SetupSession(ssl)
src/tls13.c:12252 calls AddSession(ssl)
This means that even when lifetime == 0, the ticket is still copied into the session and inserted into the cache.
2. No explicit immediate-discard branch
The ticket copy path itself does not contain a special lifetime == 0 discard branch:
That path stores the ticket data and prepares the session for caching.
There is no logic there that says "if lifetime is zero, free the ticket and skip caching".
3. Rejection happens later during resumption checks
wolfSSL performs timeout validation when the ticket is later used for resumption:
src/tls13.c:6195 calls DoClientTicketCheck(...)
src/internal.c:39520 checks:
if (diff > timeout * 1000 ||
diff > (sword64)TLS13_MAX_TICKET_AGE * 1000)
return WOLFSSL_FATAL_ERROR;
So the practical behavior is:
- receive ticket
- store ticket
- cache session
- only later, when resumption is attempted, reject it because
timeout == 0
This is not the same as immediate discard at receive time.
Why This Is Inconsistent with the RFC
The inconsistency is not about whether wolfSSL can eventually reject the ticket.
It can.
The inconsistency is about when the invalidation happens.
RFC 8446 expectation:
ticket_lifetime = 0
- ticket should be discarded immediately
- ticket should not remain as a cached resumable object
wolfSSL behavior:
ticket_lifetime = 0
- ticket is still stored and cached
- invalidation is deferred until a later resumption check
So the implementation matches the intent only partially:
- it does make the ticket unusable later
- but it does not immediately discard it when received
Practical Security Impact
By itself, this is not a high-severity cryptographic break.
The more realistic impact is cache pollution and resumption interference.
Because the ticket is cached first, a server that keeps issuing new tickets can consume shared session-cache capacity and push out other cached resumptions.
In other words, the problem is not only a wording mismatch with the RFC.
It can also become a resource-management problem.
Runtime Reproduction
To validate the practical impact, a small reproduction program was added:
The test logic is:
- Create a resumable session for logical server
A
- Confirm that
A can resume normally
- Repeatedly connect using logical server
B and keep obtaining fresh tickets
- Test whether
A can still resume
Observed results:
- with
flood=0, A resumed successfully in all rounds
- with
flood=20, some A resumptions were lost
- with
flood=50, loss became more frequent
- with
flood=100, A resumption was lost in all tested rounds
- with
flood=200, A resumption was lost in all tested rounds
This shows that cached resumptions for one logical server can be displaced by repeated new tickets associated with another logical server.
Root Cause
The root cause is the combination of two behaviors:
ticket_lifetime = 0 does not trigger an explicit discard at receive time
- session tickets are inserted into shared cache structures before later timeout validation removes them from actual use
Because of this, zero-lifetime tickets and aggressively refreshed tickets can still occupy cache entries for some period of time.
Recommended Fixes
Implementation
The clearest fix is to discard the ticket immediately when parsing NewSessionTicket if ticket_lifetime == 0.
That means:
- do not keep the ticket in the session object
- do not call the normal cache insertion path for that ticket
- treat it as non-resumable as soon as it is received
Defensive improvement
Even if the receive-time fix is not applied immediately, a second-best improvement would be:
- refuse to insert
timeout == 0 tickets into the cache
- or evict them immediately after parsing
This would still align much better with the RFC than the current deferred invalidation model.
Testing
Regression tests should cover at least these cases:
ticket_lifetime = 0 must not leave a resumable cached ticket
- repeated zero-lifetime or short-lifetime tickets should not pollute the cache
- one logical server's repeated ticket refresh should not trivially evict unrelated resumptions without bounds
Conclusion
The RFC requires that a ticket with ticket_lifetime = 0 should be discarded immediately.
wolfSSL currently stores such a ticket first and only rejects it later during resumption checks.
Therefore, the implementation is not fully consistent with the TLS 1.3 semantic requirement.
This inconsistency is small at the pure protocol-wording level, but it has a concrete operational consequence: cached resumption state can be polluted or displaced before the ticket is eventually rejected.
Reproduction steps
No response
Relevant log output
Contact Details
lxd_dong@bupt.edu.cn
Version
V5.9.1
Description
ticket_lifetime = 0and Immediate Discard SemanticsProblem Summary
This issue concerns the TLS 1.3
NewSessionTicket.ticket_lifetimefield when its value is0.The relevant rule is:
ticket_lifetimeindicates immediate discardticket_lifetime is zeroThe core question is whether wolfSSL discards such a ticket immediately, as required by the TLS 1.3 specification, or whether it still stores the ticket and only rejects it later during session resumption.
RFC 8446 Original Text
RFC 8446 Section 4.6.1, "New Session Ticket Message":
https://www.rfc-editor.org/rfc/rfc8446.html#section-4.6.1
Relevant text:
Additional surrounding text from the same section:
The important point here is that the specification does not say "reject later if used".
It says the ticket
should be discarded immediately.wolfSSL Code Behavior
1. Ticket is stored first
When a TLS 1.3
NewSessionTicketis received, wolfSSL parses the ticket and stores it into the session object:src/tls13.c:12201callsSetTicket(...)src/tls13.c:12209setsssl->timeout = lifetimesrc/tls13.c:12210setsssl->session->timeout = lifetimesrc/tls13.c:12212setsssl->session->ticketSeen = nowsrc/tls13.c:12250callsSetupSession(ssl)src/tls13.c:12252callsAddSession(ssl)This means that even when
lifetime == 0, the ticket is still copied into the session and inserted into the cache.2. No explicit immediate-discard branch
The ticket copy path itself does not contain a special
lifetime == 0discard branch:src/internal.c:35030That path stores the ticket data and prepares the session for caching.
There is no logic there that says "if lifetime is zero, free the ticket and skip caching".
3. Rejection happens later during resumption checks
wolfSSL performs timeout validation when the ticket is later used for resumption:
src/tls13.c:6195callsDoClientTicketCheck(...)src/internal.c:39520checks:So the practical behavior is:
timeout == 0This is not the same as immediate discard at receive time.
Why This Is Inconsistent with the RFC
The inconsistency is not about whether wolfSSL can eventually reject the ticket.
It can.
The inconsistency is about when the invalidation happens.
RFC 8446 expectation:
ticket_lifetime = 0wolfSSL behavior:
ticket_lifetime = 0So the implementation matches the intent only partially:
Practical Security Impact
By itself, this is not a high-severity cryptographic break.
The more realistic impact is cache pollution and resumption interference.
Because the ticket is cached first, a server that keeps issuing new tickets can consume shared session-cache capacity and push out other cached resumptions.
In other words, the problem is not only a wording mismatch with the RFC.
It can also become a resource-management problem.
Runtime Reproduction
To validate the practical impact, a small reproduction program was added:
The test logic is:
AAcan resume normallyBand keep obtaining fresh ticketsAcan still resumeObserved results:
flood=0,Aresumed successfully in all roundsflood=20, someAresumptions were lostflood=50, loss became more frequentflood=100,Aresumption was lost in all tested roundsflood=200,Aresumption was lost in all tested roundsThis shows that cached resumptions for one logical server can be displaced by repeated new tickets associated with another logical server.
Root Cause
The root cause is the combination of two behaviors:
ticket_lifetime = 0does not trigger an explicit discard at receive timeBecause of this, zero-lifetime tickets and aggressively refreshed tickets can still occupy cache entries for some period of time.
Recommended Fixes
Implementation
The clearest fix is to discard the ticket immediately when parsing
NewSessionTicketifticket_lifetime == 0.That means:
Defensive improvement
Even if the receive-time fix is not applied immediately, a second-best improvement would be:
timeout == 0tickets into the cacheThis would still align much better with the RFC than the current deferred invalidation model.
Testing
Regression tests should cover at least these cases:
ticket_lifetime = 0must not leave a resumable cached ticketConclusion
The RFC requires that a ticket with
ticket_lifetime = 0should be discarded immediately.wolfSSL currently stores such a ticket first and only rejects it later during resumption checks.
Therefore, the implementation is not fully consistent with the TLS 1.3 semantic requirement.
This inconsistency is small at the pure protocol-wording level, but it has a concrete operational consequence: cached resumption state can be polluted or displaced before the ticket is eventually rejected.
Reproduction steps
No response
Relevant log output