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
17 changes: 14 additions & 3 deletions libsshlog/bpf/sshtrace.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,22 @@
char LICENSE[] SEC("license") = "GPL";

// PROC hierarchy (101-> are created for each session):
//
// Legacy OpenSSH (pre-9.8):
// PID PARENT PID Proc
// 100 1 sshd
// 101 100 pt master
// 102 101 pt slave
// 100 1 sshd (main daemon)
// 101 100 sshd pt master (PTM)
// 102 101 sshd pt slave (PTS)
// 103 102 sh/bash or whatever
//
// OpenSSH 9.8+ with sshd-session:
// PID PARENT PID Proc
// 100 1 sshd (main daemon)
// 101 1 or 100 sshd-session pt master (PTM)
// 102 101 sshd-session pt slave (PTS)
// 103 102 sh/bash or whatever
//
// Note: sshd-session may have ppid=1 because it's exec'd rather than forked

// Swap these defines out for debugging. Logs can be seen with:
// sudo cat /sys/kernel/debug/tracing/trace_pipe
Expand Down
35 changes: 31 additions & 4 deletions libsshlog/proc_parsers/existing_connections.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
// Copyright 2023- by Open Kilt LLC. All rights reserved.
// This file is part of the SSHLog Software (SSHLog)
// Licensed under the Redis Source Available License 2.0 (RSALv2)
//
// OpenSSH 9.8+ Compatibility:
// OpenSSH 9.8 introduced a new process model where sshd exec's a separate
// sshd-session binary (/usr/lib/openssh/sshd-session) for each connection.
// This changes the process hierarchy and requires updated detection logic
// to correctly identify PTS (pseudo-terminal slave) processes.
//
// Supported hierarchies:
// - Legacy (pre-9.8): sshd (ppid=1) -> sshd (PTM) -> sshd (PTS) -> shell
// - OpenSSH 9.8+: sshd-session (ppid=1) -> sshd-session (PTS) -> shell
// - OpenSSH 9.8+ (4-level): sshd (ppid=1) -> sshd-session -> sshd-session (PTM) -> sshd-session (PTS) -> shell

#include "existing_connections.h"
#include "pfs/procfs.hpp"
Expand Down Expand Up @@ -155,11 +166,11 @@ std::vector<struct ssh_session> ExistingConnections::get_sessions() { return thi

static bool is_pts(pfs::task& process, std::unordered_map<int32_t, pfs::task> processes) {
try {
// Make sure target proc is sshd
// Make sure target proc is sshd or sshd-session
if (!is_sshd_process(process.get_comm()))
return false;

// Make sure he has a parent
// Make sure process has a parent (and it's not init)
int ppid = process.get_stat().ppid;
if (ppid == 1)
return false;
Expand All @@ -171,26 +182,42 @@ static bool is_pts(pfs::task& process, std::unordered_map<int32_t, pfs::task> pr

// Check parent details (can throw if parent died)
int parent_ppid = parent.get_stat().ppid;
if (!is_sshd_process(parent.get_comm()) || parent_ppid == 1)
if (!is_sshd_process(parent.get_comm()))
return false;

// OpenSSH 9.8+ support: In the new model, sshd-session (PTM) may have ppid=1
// because it's exec'd rather than forked. This is valid for a 2-level hierarchy:
// sshd-session (PTM, ppid=1) -> sshd-session (PTS, ppid=PTM) -> bash
if (parent_ppid == 1)
return true;

if (processes.find(parent_ppid) == processes.end())
return false;

// Grandparent should exist
// Grandparent should exist and be sshd/sshd-session
pfs::task grandparent = processes.at(parent_ppid);
int grandparent_ppid = grandparent.get_stat().ppid;

if (!is_sshd_process(grandparent.get_comm()))
return false;

// 3-level hierarchy: sshd (main, ppid=1) -> sshd (PTM) -> sshd (PTS)
if (grandparent_ppid == 1)
return true;

// OpenSSH 9.8+ support: Handle 4-level hierarchy
// sshd (main, ppid=1) -> sshd-session (conn handler) -> sshd-session (PTM) -> sshd-session (PTS)
if (processes.find(grandparent_ppid) != processes.end()) {
pfs::task great_grandparent = processes.at(grandparent_ppid);

// If great-grandparent is NOT sshd, we've reached the PTS at 3 levels
if (!is_sshd_process(great_grandparent.get_comm()))
return true;

// If great-grandparent IS sshd/sshd-session and has ppid=1, this is a 4-level hierarchy
int great_grandparent_ppid = great_grandparent.get_stat().ppid;
if (great_grandparent_ppid == 1)
return true;
}

return false;
Expand Down