-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Critical - P2
-
Affects Version/s: None
-
Component/s: None
-
Server Security
-
Fully Compatible
-
ALL
-
v9.0, v8.3, v8.0, v7.0, v8.2
-
-
Server Security 2026-07-03, Server Security 2026-07-17, Server Security 2026-07-31, Server Security 2026-08-14, Server Security 2026-08-28, Server Security 2026-09-11, Server Security 2026-10-23, Server Security 2026-11-06, Server Security 2026-11-20
-
None
-
None
-
None
-
None
-
None
-
None
-
None
Related to CVE-690 Engineering Work and SECBUG-1282
Security Context
See SECBUG-1282 for problem description, security impact, affected conditions, reproduction steps, and severity rationale.
Root Cause
- src/mongo/db/commands/authentication_commands.cpp: user-existence check collapses all non-OK results into "user absent" via .isOK(); ErrorCodes::UserNotFound and transient failures (MaxTimeMSExpired, NotPrimary, lock timeout, Interrupted) are indistinguishable to the caller
- src/mongo/db/auth/sasl_x509_server_conversation.cpp: same fail-open pattern in the SASL X.509 path
- src/mongo/db/auth/authorization_router_impl.cpp: the function-try-block in acquireUser catches every DBException and returns it as a Status, so all exceptions reach the caller as a non-OK Status
- On v7.0 and v8.0 the lookup uses am->getUserDescription(opCtx, request.name, &ignored).isOK() instead of acquireUser; the same fail-open applies because any non-OK status from the underlying findOne maps to userExists = false
Proposed Fix
Update both paths to handle the acquireUser() (or getUserDescription()) status explicitly:
- OK: treat as explicit user exists, reject authentication per separation policy
- ErrorCodes::UserNotFound: treat as explicit user absent, allow cluster-member path
- any other error: fail closed (abort authentication, do not grant internal authorization)
auto acquireResult = am->acquireUser(opCtx, std::move(userRequest));
if (!acquireResult.isOK() &&
acquireResult.getStatus().code() != ErrorCodes::UserNotFound) {
uassertStatusOK(acquireResult.getStatus());
}
bool userExists = acquireResult.isOK();
Apply the same pattern to the getUserDescription call sites in v7.0 and v8.0. Prefer a shared helper to keep the command and SASL behavior consistent.
Acceptance Criteria
- Authentication must fail closed when separation-check user lookup returns non-UserNotFound errors.
- Authentication must continue to reject explicit user overlap when lookup returns OK.
- Authentication must continue to allow cluster-member flow when lookup returns UserNotFound.
- No code path may call grantInternalAuthorization() after an ambiguous/transient lookup failure.
Testing
- Add unit tests for both:
- src/mongo/db/commands/authentication_commands.cpp
- src/mongo/db/auth/sasl_x509_server_conversation.cpp
- Validate three-way behavior of separation check:
- OK}} -> reject as explicit user exists
- UserNotFound -> allow cluster-member auth
- Any other error -> reject/fail authentication
- Add/extend integration test coverage in X.509 auth tests to assert no internal authorization is granted on transient lookup errors.
Likely Files
- src/mongo/db/commands/authentication_commands.cpp
- src/mongo/db/auth/sasl_x509_server_conversation.cpp
- src/mongo/db/auth/sasl_x509_test.cpp
- jstests/ssl/x509_enforce_user_cluster_separation.js (if integration-level repro/assertion is added)
Note on v7.0/v8.0 backport
The v7.0 and v8.0 fix targets getUserDescription instead of acquireUser. The fix shape is the same — inspect the returned Status code before assigning userExists — but verify which non-UserNotFound statuses getUserDescription can return on those branches (e.g. storage errors during findOne) and confirm that propagating them as authentication failures is safe.