Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Sources/NIOSSH/SSHMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,10 @@ extension ByteBuffer {
}
type = .signal(signalName)
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
}

Expand Down
60 changes: 60 additions & 0 deletions Tests/NIOSSHTests/SSHMessagesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -797,4 +797,64 @@ final class SSHMessagesTests: XCTestCase {

try self.assertCorrectlyManagesPartialRead(message)
}

func testUnknownChannelRequestWithPayload() throws {
// This test verifies that unknown channel request types with payloads
// are handled correctly without corrupting the parser state.
var buffer = ByteBufferAllocator().buffer(capacity: 100)

// Build a channel request with unknown type and payload
buffer.writeInteger(SSHMessage.ChannelRequestMessage.id)
buffer.writeInteger(UInt32(0)) // Recipient channel
buffer.writeSSHString("custom-extension@example.com".utf8) // Unknown type
buffer.writeSSHBoolean(false) // Want reply
buffer.writeSSHString("extension-payload-data".utf8) // Payload

let message = try buffer.readSSHMessage()

guard case .some(.channelRequest(let request)) = message else {
XCTFail("Expected channelRequest message")
return
}

XCTAssertEqual(request.recipientChannel, 0)
XCTAssertEqual(request.type, .unknown)
XCTAssertFalse(request.wantReply)

// Verify buffer is fully consumed (no leftover bytes that would corrupt next read)
XCTAssertEqual(buffer.readableBytes, 0, "Parser should consume all bytes for unknown channel request")
}

func testParserContinuesAfterUnknownChannelRequest() throws {
// This test verifies that the parser can continue reading valid messages
// after encountering an unknown channel request type.
var buffer = ByteBufferAllocator().buffer(capacity: 200)

// Write unknown channel request
buffer.writeInteger(SSHMessage.ChannelRequestMessage.id)
buffer.writeInteger(UInt32(0))
buffer.writeSSHString("unknown-request@example.com".utf8)
buffer.writeSSHBoolean(false)
buffer.writeSSHString("payload-data".utf8)

// Read first message
let message1 = try buffer.readSSHMessage()
XCTAssertNotNil(message1)
guard case .some(.channelRequest(let request)) = message1 else {
XCTFail("Expected channelRequest")
return
}
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

buffer.writeInteger(SSHMessage.ChannelSuccessMessage.id)
buffer.writeInteger(UInt32(0))

// Verify parser can continue
let message2 = try buffer.readSSHMessage()
guard case .some(.channelSuccess) = message2 else {
XCTFail("Expected channelSuccess after unknown channel request - parser state may be corrupted")
return
}
}
}
Loading