Skip to content

Fix parser corruption on unknown channel request types#222

Open
jeffellin wants to merge 1 commit intoapple:mainfrom
jeffellin:fix/channel-request-corruption
Open

Fix parser corruption on unknown channel request types#222
jeffellin wants to merge 1 commit intoapple:mainfrom
jeffellin:fix/channel-request-corruption

Conversation

@jeffellin
Copy link
Copy Markdown

Fixes #221

Summary

This PR fixes the critical parser state corruption bug when NIOSSH receives SSH channel requests with unknown request types. See #221 for detailed problem analysis and reproduction steps.

Changes

  • readChannelRequestMessage(): Consume remaining bytes with moveReaderIndex(to: writerIndex) before setting type = .unknown
  • Add testUnknownChannelRequestWithPayload to verify bytes are consumed
  • Add testParserContinuesAfterUnknownChannelRequest to verify parser state remains valid

This matches the existing pattern in readGlobalRequestMessage() (lines 814-823) which correctly consumes payload bytes for unknown global requests.

Root Cause

When the parser encountered an unknown channel request type (e.g., vendor-specific extensions like session-id@example.com), it would set type = .unknown but fail to consume the payload bytes from the buffer. The unconsumed bytes would corrupt the next packet read, causing CryptoKitError.authenticationFailure on all subsequent SSH messages and making the connection permanently unusable.

Solution

Apply the same buffer-consumption pattern used for unknown global requests:

default:
    // Unknown channel request type - consume remaining bytes to prevent
    // parser state corruption. Without this, leftover bytes cause
    // invalidPacketFormat errors that corrupt the encryption state.
    self.moveReaderIndex(to: self.writerIndex)
    type = .unknown

Test Results

Before fix:

  • testUnknownChannelRequestWithPayload: ❌ FAILS - 24 unconsumed bytes in buffer
  • testParserContinuesAfterUnknownChannelRequest: ❌ FAILS - throws unknownType(0) error

After fix:

  • testUnknownChannelRequestWithPayload: ✅ PASSES - all bytes consumed
  • testParserContinuesAfterUnknownChannelRequest: ✅ PASSES - parser continues normally

Full test suite:

$ swift test
Executed 322 tests, with 0 failures

$ swift test \
  --explicit-target-dependency-import-check error \
  -Xswiftc -require-explicit-sendable \
  -Xswiftc -warnings-as-errors
Executed 322 tests, with 0 failures

Breaking Changes

None. This is a pure bug fix:

  • Still returns SSHMessage.channelRequest with type = .unknown
  • No API changes
  • No behavior changes for known request types
  • Simply fixes buffer corruption that made connections unusable

Impact

This fix enables NIOSSH to interoperate with:

  • Enterprise SSH servers with session tracking extensions
  • Cloud provider managed SSH (AWS Session Manager, Azure Bastion, GCP IAP)
  • Security appliances with audit/recording extensions
  • GitHub Enterprise with custom authentication extensions
  • Any SSH server using RFC 4250 name@domain extension format

Checklist

  • Tests added and all passing
  • Commit message follows template
  • No breaking changes
  • Matches existing code patterns
  • RFC 4250 compliant

Motivation:

NIOSSH suffers from critical parser state corruption when receiving
SSH channel requests with unknown request type strings. The parser
fails to consume payload bytes, leaving unconsumed data that corrupts
all subsequent packet reads, causing permanent connection failure.

This blocks production usage with enterprise SSH servers, cloud
providers, and security appliances that send vendor-specific channel
request extensions using the RFC 4250 name@domain format.

Modifications:

- readChannelRequestMessage(): Consume remaining bytes with
  moveReaderIndex(to: writerIndex) before setting type = .unknown
- Add testUnknownChannelRequestWithPayload to verify bytes consumed
- Add testParserContinuesAfterUnknownChannelRequest to verify parser
  state remains valid

This matches the existing pattern in readGlobalRequestMessage() which
correctly consumes payload bytes for unknown global requests.

Result:

NIOSSH can now handle SSH channel requests with unknown request types
without connection corruption. Connections continue normally when
encountering vendor-specific extensions, enabling interoperability with
enterprise SSH servers, cloud providers, and security appliances.
}
XCTAssertEqual(request.type, .unknown)

// Write a known message after
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Write a known message after
// Write a known message afterward

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.

Parser corruption on unknown channel request types causes connection failure

2 participants