Skip to content

ssh: use errors.As for PartialSuccessError unwrapping in auth loop#352

Open
XananasX7 wants to merge 1 commit into
golang:masterfrom
XananasX7:fix/ssh-banner-partial-success-unwrap
Open

ssh: use errors.As for PartialSuccessError unwrapping in auth loop#352
XananasX7 wants to merge 1 commit into
golang:masterfrom
XananasX7:fix/ssh-banner-partial-success-unwrap

Conversation

@XananasX7
Copy link
Copy Markdown

Problem

The authentication loop in serverAuthenticate detected *PartialSuccessError using a direct type assertion:

if partialSuccess, ok := authErr.(*PartialSuccessError); ok {

This is inconsistent with how *BannerError is detected in the same loop, which already uses errors.As to unwrap error chains. When an authentication callback returns a BannerError wrapping a PartialSuccessError — a natural combination when a server wants to display a banner and require a second authentication factor — two bugs occur:

Bug 1 — public-key query path (isQuery=true):
The wrapped PartialSuccessError is not recognised, so the server never sends SSH_MSG_USERAUTH_PK_OK. The client therefore never sends the actual signature, and the partial success is never surfaced to the outer auth loop at all.

Bug 2 — main result path:
Even if the query phase were fixed, the main result check also uses a direct type assertion and misses the wrapped value, incrementing authFailures and sending an ordinary failure reply instead of a partial-success reply with the updated allowed-methods list.

The net effect: a server callback that returns &BannerError{Err: &PartialSuccessError{Next: ...}, Message: "..."}} will have the banner sent correctly but the multi-factor continuation silently dropped — the client receives an auth failure and the PartialSuccessError.Next callbacks are never used.

Fix

Replace both direct type assertions with errors.As calls, matching the pattern already used for BannerError:

// isQuery path (was: _, isPartialSuccessError := candidate.result.(*PartialSuccessError))
var _partialSuccessForQuery *PartialSuccessError
isPartialSuccessError := errors.As(candidate.result, &_partialSuccessForQuery)

// result path (was: if partialSuccess, ok := authErr.(*PartialSuccessError); ok {)  
var partialSuccess *PartialSuccessError
if errors.As(authErr, &partialSuccess) {

Test

Added TestBannerWrappingPartialSuccess to server_multi_auth_test.go. It configures a server whose PublicKeyCallback returns BannerError{Err: PartialSuccessError{Next: {PasswordCallback: ...}}}, then verifies that:

  • the banner is delivered to the client
  • the client completes the password second factor
  • the connection is established successfully

The test fails on the unpatched code and passes with this change. All existing ssh/... tests continue to pass.

Note for reviewers

This is a purely behavioural fix with no security-boundary change — an attacker cannot exploit this to bypass authentication (they still receive a failure). The impact is that servers implementing MFA flows using BannerError + PartialSuccessError together would silently break until reported.

The authentication loop in serverAuthenticate used a direct type assertion
to detect *PartialSuccessError:

    if partialSuccess, ok := authErr.(*PartialSuccessError); ok {

This is inconsistent with how *BannerError is detected in the same loop,
which correctly uses errors.As to unwrap the error chain.

When an authentication callback returns a BannerError wrapping a
PartialSuccessError, two bugs appear:

1. In the isQuery (public-key probe) path, the wrapped PartialSuccessError
   is not recognised, so the server never sends SSH_MSG_USERAUTH_PK_OK.
   The client therefore never sends the actual signature, and the
   PartialSuccess is never surfaced to the authentication loop at all.

2. In the main result path, the wrapped PartialSuccessError is similarly
   missed, so authFailures is incremented instead of switching authConfig
   to the Next callbacks and sending a partial-success reply.

Replace both direct type assertions with errors.As calls, consistent with
the existing BannerError handling, and add a regression test.

Change-Id: fix-banner-partial-success-unwrap
@google-cla
Copy link
Copy Markdown

google-cla Bot commented Jun 3, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant