diff --git a/libsshlog/bpf/sshtrace.bpf.c b/libsshlog/bpf/sshtrace.bpf.c index a096821..bfc8848 100644 --- a/libsshlog/bpf/sshtrace.bpf.c +++ b/libsshlog/bpf/sshtrace.bpf.c @@ -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 diff --git a/libsshlog/proc_parsers/existing_connections.cpp b/libsshlog/proc_parsers/existing_connections.cpp index a9124f6..b927ef7 100644 --- a/libsshlog/proc_parsers/existing_connections.cpp +++ b/libsshlog/proc_parsers/existing_connections.cpp @@ -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" @@ -155,11 +166,11 @@ std::vector ExistingConnections::get_sessions() { return thi static bool is_pts(pfs::task& process, std::unordered_map 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; @@ -171,26 +182,42 @@ static bool is_pts(pfs::task& process, std::unordered_map 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;