Security
This release fixes eight security issues found in an internal audit of the codebase. The potentially most serious allows a client to inject headers that the protected backend application trusts as authenticated identity claims, on request paths the module is configured to answer without authenticating. The others cover session fixation on login, reflected cross-site scripting in two generated pages, CSRF on the discovery response, unauthenticated token revocation via front-channel logout, injection of duplicate protocol parameters into the authorization request, replayable back-channel logout tokens, and credentials written verbatim to the debug log.
No exploitation has been observed. All issues were found internally; none were reported by an external party
See: GHSA-gcc4-wwpg-r8q8
Three of these change behaviour an operator may notice: scopes and auth_request_params passed on a 3rd-party initiated login URL are now ignored (with a warning logged) unless the Discovery response passes CSRF validation; an OIDCAuthRequestParams entry that collides with a parameter the module sets now replaces it instead of being sent alongside it; and a front-channel logout no longer revokes the session's tokens at the provider.
Bugfixes
- session: recognize whether a stored payload is compressed instead of assuming the local setting, so a session survives a change to
OIDC_JWT_INTERNAL_NO_COMPRESSand is readable across servers built with and without compression - metadata: avoid a NULL pointer dereference (segfault) when the OP selection/discovery page is served while no
OIDCMetadataDiris configured, and catch an RP vhost that configuresOIDCRedirectURIwithout any provider source at startup - cache: give each process-local cache (parsed sessions, flattened claims, provider metadata, JWKs selections, compiled
Requireregexes) its own memory pool. All five allocated from one shared pool while each serialized on its own lock, so on a threaded MPM (event,worker) two requests working in different caches could be inside that pool at the same time and corrupt it, with effects ranging from wrong claim values to a segfault; the parsed-session and claims caches sit on the authenticated-request path, so this was reachable under ordinary concurrent load.preforkwas never affected - cache: stop a worker's memory growing for the life of the process because of the JWKs selection cache. It handed each request the imported keys by reference and so could never free them: a replaced or purged entry retired its keys to a list released only at process exit, so every
OIDCJWKSRefreshInterval(3600 seconds by default) retired another full key set per cached selection, scaling with the number of providers and key ids and never coming back until the child was recycled. The cache now stores the selection serialized and each request parses its own copy, at the cost of importing the one or two keys that matched - cache: fix a data race on the process-local caches' last-access stamp, which was written while only the read lock was held, so concurrent readers could update the same field at once; it is now a whole-second value updated atomically, which is the resolution the eviction order already used
- cache: do not leak a process-local cache's private memory pool when its creation fails part-way, and count an entry dropped by a rebuild that declined to produce a value towards the periodic reclaim that frees its interned key, which repeated failures could otherwise defer indefinitely
- cache: fail a file cache write when the value could not be fully written, instead of reporting success and leaving a truncated entry that only fails on the next read
- cfg: accept
OIDCSessionCookieChunkSize 0, which the documentation and the directive help both describe as the way to disable chunking and which the runtime already honours, but which the parser rejected - cache: do not lose a cached value whose first two bytes happen to look like a compressed-payload header; on a build that stores values uncompressed (neither zlib nor brotli, or
OIDC_JWT_INTERNAL_NO_COMPRESSset) a value starting with one of 19 two-character prefixes such asxorH,was taken for a compressed payload, failed to decompress, and was dropped as a cache miss
Features
- session: stamp the session payload with a format version, so a session written by a newer version is discarded rather than misread; sessions written before this carry no version and still load, leaving upgrade and downgrade unaffected
- metrics: add counters and timers for the remaining outbound provider calls and operational events: JWKs retrieval, pushed authorization requests, dynamic client registration, token revocation, DPoP nonce retries, back-channel logout outcomes, session-cache-to-cookie fallbacks and cache operation retries
Performance
- release the cross-process refresh mutex while backing off for a refresh another caller is performing, so one waiter no longer blocks every refresh on the machine
- re-check the entry under the write lock when replacing a process-local cache entry, so concurrent refreshers no longer each rebuild it
- metrics: flush on a scratch pool and serve a scrape from the request pool, rather than copying and re-serializing the whole document onto the never-cleared process pool
- grow the decompression buffer by doubling rather than in fixed steps: inflating a payload up to the 10MB limit took around 1280 rounds and left 6.25GB of intermediate buffers behind in the request pool, which is the outcome that limit exists to prevent; it now costs under 30MB
Commercial
- commercial subscription based support for large enterprise businesses is available via sales@openidc.com
- licensed binary packages for various other platforms such as Microsoft Windows, Red Hat Enterprise Linux 7, older Ubuntu and Debian distros, Oracle HTTP Server 12.x/14.x and IBM HTTP Server 9.x, are available under a commercial license and agreement via sales@openidc.com
- support for Redis/Valkey over TLS, Redis/Valkey (TLS) Sentinel, and Redis/Valkey (TLS) Cluster is available under a commercial license and agreement via sales@openidc.com
The RPM packages below are signed with the following RSA PGP key:
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBGh53lgBCADCyoOkfnE5h5rBLlf02oFpI/z2vUXK5W4T56xnNPu0/iIOxbBk
YX9rSypZFhfjv28lhGgelWEg28Ab/Yxs6l0obCgDEuFUDQ5Dv+N+YSMy67vtLwYW
9LM5p9fMN9bXOa62PwvtzRzh+xRyRBcIfMacGJC+SqUK6QhzC0lNwCsr1OaWjzon
mkaodwrloNMxEZVvFn63PvuQDZ3wwQty+0XpYiiChMssGBn6nmPDQJ7pDtQDkhfD
Z5FKY6K7AQJ4fneiVCLGngPBwTXBGcfWa+Y0HCS2ghQwDO6jYXd5GjowVDTjfMK3
QJ3e26Ox9X3V0Fl04R1i5EthEkAWGfy1lksvABEBAAG0HU9wZW5JREMgPHN1cHBv
cnRAb3BlbmlkYy5jb20+iQFRBBMBCgA7FiEEFdjWJA1IGDkAITSxnyZY1L0OSOMF
Amh53lgCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQnyZY1L0OSONG
Jgf+II0wG96R0g28Kp+R4AYzSdX0CEqr6OhwHHw4cFpLsHxZNhojo7I4OnLKEdfc
lFl37rE+hG3QpzD/b4S/fpPjd4hcLkguBQxtdxqZZVAIT8HWbveHRkI8MNnjOPwv
Hy6jBncMs1IT/URV2si/Q34+PLo8tvo/lXNa16svVl2DoYXO8MCszgCE1bx055EF
XPh4Teu5Y4OLHECSicMxrmN746dAD121zy4bLLx9mZ0erhLjvkj1vkFmlHFKyvwY
/pbSqXs9hW/wweW1oQ/xEIJWWS71PeoutUBjr0WC4sILnR5PBPZplgNh297Qex6g
qaW3io0tCH9KxU1tXYn/iL/hbQ==
=mlOy
-----END PGP PUBLIC KEY BLOCK-----