Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ All notable user-visible changes should be recorded here.
result invariants.
- Added an optional Clang libFuzzer parser target with a sanitized seed corpus
and bounded Ubuntu CI smoke campaign.
- Added a dedicated util-linux `login` handler for selected failed-login,
retry-exhaustion, session-failure, local-login, and root-login messages.
- Expanded the sanitized mixed auth corpus from 150 to 160 lines with ten
fixture-backed `login` records and updated parser coverage telemetry.

### Changed

Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set(LOGLENS_LIBRARY_SOURCES
src/config.cpp
src/parser.cpp
src/parser/failure_classifier.cpp
src/parser/login_handlers.cpp
src/parser/pam_handlers.cpp
src/parser/program_dispatch.cpp
src/parser/source_envelope_parser.cpp
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ LogLens currently parses and reports these additional auth patterns beyond the c
- `sshd`-owned `PAM: Authentication failure ...` lines, including OpenSSH's optional leading `error:` marker, with invalid/illegal-user variants normalized to `ssh_invalid_user`
- `sudo` command, password-failure, and sudoers policy-denial audit lines
- `su` success and failure audit lines
- selected util-linux `login` failure, retry-exhaustion, session-failure,
local-login, and root-login audit lines
- `pam_unix(...:auth): authentication failure`
- `pam_unix(...:session): session opened`
- selected `pam_faillock(...:auth)` failure variants
Expand Down
10 changes: 10 additions & 0 deletions assets/mixed_auth_corpus.log
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,13 @@ Mar 12 08:09:45 mixed-auth-10 cron[5008]: (root) CMD (/usr/bin/true)
Mar 12 08:09:49 mixed-auth-10 sshd[5009]: Failed password for invalid user user010 from 999.0.113.106 port 51905 ssh2

Foo 12 08:09:53 mixed-auth-10 sshd[5010]: Failed password for invalid user user010 from 203.0.113.107 port 51906 ssh2
Mar 12 08:10:01 mixed-auth-11 login[5101]: FAILED LOGIN 1 FROM tty1 FOR user011, Authentication failure
Mar 12 08:10:05 mixed-auth-11 login[5102]: TOO MANY LOGIN TRIES (3) FROM tty1 FOR user011, Authentication failure
Mar 12 08:10:09 mixed-auth-11 login[5103]: FAILED LOGIN SESSION FROM tty1 FOR user011, Authentication failure
Mar 12 08:10:13 mixed-auth-11 login[5104]: LOGIN ON tty1 BY user011
Mar 12 08:10:17 mixed-auth-11 login[5105]: ROOT LOGIN ON tty2
Mar 12 08:10:21 mixed-auth-12 login[5201]: FAILED LOGIN 1 FROM tty3 FOR user012, Authentication failure
Mar 12 08:10:25 mixed-auth-12 login[5202]: TOO MANY LOGIN TRIES (3) FROM tty3 FOR user012, Authentication failure
Mar 12 08:10:29 mixed-auth-12 login[5203]: FAILED LOGIN SESSION FROM tty3 FOR user012, Authentication failure
Mar 12 08:10:33 mixed-auth-12 login[5204]: LOGIN ON tty3 BY user012 FROM example-console
Mar 12 08:10:37 mixed-auth-13 login[5301]: LOGIN ON tty4 BY user013
13 changes: 7 additions & 6 deletions assets/mixed_auth_parser_coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
"input_mode": "syslog_legacy",
"assume_year": 2026,
"parser_quality": {
"total_input_lines": 150,
"total_lines": 140,
"total_input_lines": 160,
"total_lines": 150,
"skipped_blank_lines": 10,
"parsed_lines": 90,
"parsed_lines": 100,
"unparsed_lines": 50,
"parse_success_rate": 0.6428571429,
"parse_success_rate": 0.6666666667,
"top_unknown_patterns": [
{"pattern": "invalid_month_token", "count": 10},
{"pattern": "malformed_source_ip", "count": 10},
Expand All @@ -26,13 +26,14 @@
{"category": "unsupported_pam_variant", "count": 10}
]
},
"parsed_event_count": 90,
"parsed_event_count": 100,
"warning_count": 50,
"event_type_counts": [
{"event_type": "ssh_accepted_publickey", "count": 10},
{"event_type": "ssh_invalid_user", "count": 10},
{"event_type": "ssh_failed_publickey", "count": 10},
{"event_type": "pam_auth_failure", "count": 30},
{"event_type": "pam_auth_failure", "count": 36},
{"event_type": "session_opened", "count": 4},
{"event_type": "sudo_command", "count": 10},
{"event_type": "sudo_auth_failure", "count": 10},
{"event_type": "su_auth_failure", "count": 10}
Expand Down
2 changes: 1 addition & 1 deletion docs/case-study-parser-uncertainty-as-evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ the narrower engineering question.

The checked-in
[`mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) is a sanitized,
150-line syslog-style fixture. Its paired
160-line syslog-style fixture. Its paired
[`mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json)
records recognized events, warnings, blank lines, failure categories, pattern
buckets, and source-line references.
Expand Down
6 changes: 5 additions & 1 deletion docs/parser-conformance-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ coverage of every distro or PAM module wording.
| `sshd` | `sshd[pid]: message` or `sshd: message` | Failed password, failed password for invalid/illegal user, failed none for invalid/illegal user, direct invalid/illegal user, `input_userauth_request` invalid/illegal user, accepted password, accepted publickey, accepted keyboard-interactive/pam, failed publickey, failed keyboard-interactive/pam, maximum-authentication-attempts exceeded, and `sshd`-owned `PAM: Authentication failure` lines | `ssh_failed_password`, `ssh_invalid_user`, `ssh_accepted_password`, `ssh_accepted_publickey`, `ssh_accepted_keyboard_interactive`, `ssh_failed_publickey`, `ssh_failed_keyboard_interactive`, `ssh_max_auth_tries`, `pam_auth_failure` | Preauth close/reset, timeout/disconnection, negotiation failure, and other unsupported `sshd` messages remain parser warnings such as `sshd_connection_closed_preauth`, `sshd_timeout_or_disconnection`, `sshd_negotiation_failure`, or `sshd_other` |
| `sudo` | `sudo[pid]: <actor> : ...` or `sudo: <actor> : ...` | Command audit lines with `COMMAND=`, incorrect-password audit lines, user-not-in-sudoers denials, and command-not-allowed denials | `sudo_command`, `sudo_auth_failure`, `sudo_policy_denied` | Other well-formed sudo-like messages remain `sudo_other` parser warnings and do not count as sudo burst evidence |
| `su` | `su[pid]: message` or `su: message` | `FAILED SU (to <target>) <actor> on <tty>` and `Successful su for <target> by <actor>` | `su_auth_failure`, `session_opened` | Other well-formed `su` messages remain `su_other` parser warnings |
| `login` | `login[pid]: message` or `login: message` | Selected util-linux `FAILED LOGIN`, `TOO MANY LOGIN TRIES`, `FAILED LOGIN SESSION`, `LOGIN ON ... BY ...`, and `ROOT LOGIN ON ...` messages | `pam_auth_failure`, `session_opened` | Localized or other unmodeled `login` messages remain `login_other` parser warnings; no network source IP is inferred from the `FROM` terminal/host field |
| `pam_unix` | `pam_unix(<service>:auth): ...` or `pam_unix(<service>:session): ...` | Auth failures carrying `authentication failure` plus selected session-opened lines such as sudo/su session opens | `pam_auth_failure`, `session_opened` | Session-closed and other unsupported `pam_unix` messages remain `pam_unix_session_closed` or `pam_unix_other` parser warnings |
| `pam_faillock` | `pam_faillock(<service>:auth): ...` | Selected auth failure variants: `Consecutive login failures for user ... from <ip>` and `Authentication failure for user ... from <ip>` | `pam_auth_failure` | Account-lock telemetry, auth-success telemetry, and other unmodeled variants remain `pam_faillock_account_locked`, `pam_faillock_authsucc`, or `pam_faillock_other` warnings |
| `pam_sss` | `pam_sss(<service>:auth): ...` | Selected SSSD auth failure variant: `received for user <user>: 7 (Authentication failure)` | `pam_auth_failure` | Unknown-user, auth-info-unavailable, and other unmodeled variants remain `pam_sss_unknown_user`, `pam_sss_authinfo_unavail`, or `pam_sss_other` warnings |
Expand Down Expand Up @@ -80,6 +81,8 @@ coverage of every distro or PAM module wording.
| Sudo policy denial | `<actor> : user NOT in sudoers ...` or `<actor> : command not allowed ...` | `syslog_legacy`, `journalctl_short_full` | `sudo_policy_denied` | Audit event; not counted as sudo burst evidence by default. |
| `su` failure audit | `FAILED SU (to <target>) <actor> on <tty>` | `syslog_legacy`, `journalctl_short_full` | `su_auth_failure` | Normalized `username` is the actor. |
| `su` success audit | `Successful su for <target> by <actor>` | `syslog_legacy`, `journalctl_short_full` | `session_opened` | Normalized `username` is the actor. |
| util-linux `login` failure audit | `FAILED LOGIN ... FROM <terminal-or-host> FOR <user>, ...`, `TOO MANY LOGIN TRIES ...`, or `FAILED LOGIN SESSION ...` | `syslog_legacy`, `journalctl_short_full` | `pam_auth_failure` | Extracts `username`; source IP remains empty because `FROM` is not treated as verified network evidence. |
| util-linux `login` success audit | `LOGIN ON <terminal> BY <user> [FROM <host>]` or `ROOT LOGIN ON <terminal> [FROM <host>]` | `syslog_legacy`, `journalctl_short_full` | `session_opened` | Extracts the actor, or `root` for root-login records; does not imply remote attribution. |

## Unsupported Bucket Matrix

Expand All @@ -102,6 +105,7 @@ normalized event is always `none`.
| Other unsupported `pam_sss(...)` messages | `syslog_legacy`, `journalctl_short_full` | `unsupported_pam_variant` | `pam_sss_other` | none |
| Well-formed `sudo` line that is not command, incorrect-password, or policy-denial evidence | `syslog_legacy`, `journalctl_short_full` | `known_program_unknown_message` | `sudo_other` | none |
| Well-formed `su` line that is not recognized as success or failure audit evidence | `syslog_legacy`, `journalctl_short_full` | `known_program_unknown_message` | `su_other` | none |
| Well-formed `login` line outside the selected util-linux message shapes | `syslog_legacy`, `journalctl_short_full` | `known_program_unknown_message` | `login_other` | none |
| Well-formed unsupported program tag | `syslog_legacy`, `journalctl_short_full` | `unknown_program` | `program_<sanitized_program>` | none |

## Header And Structural Warning Matrix
Expand Down Expand Up @@ -133,7 +137,7 @@ coverage telemetry path.
| [`assets/parser_auth_families_syslog.log`](../assets/parser_auth_families_syslog.log) | Selected `sshd`, `pam_unix`, `pam_faillock`, `pam_sss`, and session-opened auth-family support, plus five unsupported PAM-family telemetry buckets |
| [`assets/parser_auth_families_journalctl_short_full.log`](../assets/parser_auth_families_journalctl_short_full.log) | Same auth-family event and warning shape as the syslog auth-family fixture, with journalctl timestamp parsing |
| [`assets/noisy_auth_sample.log`](../assets/noisy_auth_sample.log) and [`tests/fixtures/parser_matrix/noisy_auth_expected.json`](../tests/fixtures/parser_matrix/noisy_auth_expected.json) | Noisy syslog coverage fixture with malformed lines, blank lines, unsupported auth-family evidence, irrelevant service lines, and locked parser quality counts |
| [`assets/mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) and [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) | 150-line sanitized mixed syslog corpus with Ubuntu / Debian-style `auth.log` and RHEL-family `secure` host labels, 90 parsed events, 50 parser warnings, 10 blank lines, and locked unknown-pattern and failure-category coverage |
| [`assets/mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) and [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) | 160-line sanitized mixed syslog corpus with Ubuntu / Debian-style `auth.log`, RHEL-family `secure` labels, selected util-linux `login` records, 100 parsed events, 50 parser warnings, 10 blank lines, and locked unknown-pattern and failure-category coverage |

## Review Rule

Expand Down
10 changes: 8 additions & 2 deletions docs/parser-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ The parser currently recognizes common authentication evidence from:
- `sshd`
- `sudo`
- `su`
- `login`
- `pam_unix(...)`
- selected `pam_faillock(...)` variants
- selected `pam_sss(...)` variants

Recognized SSH failure families include failed password, invalid user, illegal user, failed publickey, failed keyboard-interactive/pam, failed-none invalid-user probing, `input_userauth_request` invalid/illegal-user preauth traces, `sshd`-owned PAM authentication-failure lines, and maximum-authentication-attempts-exceeded lines. `illegal user` is treated as an OpenSSH wording variant of `invalid user`. Maximum-authentication-attempts and `sshd`-owned PAM authentication-failure lines may include OpenSSH's leading `error:` marker and still normalize into the same event family. Invalid or illegal-user variants of failed-none probing, `input_userauth_request` preauth traces, keyboard-interactive, `sshd`-owned PAM authentication failures, and maximum-authentication-attempts-exceeded lines are normalized into `ssh_invalid_user` events. Recognized SSH failures can become detection signals through the configured signal mapping.

Recognized success or audit families include accepted password, accepted publickey, accepted keyboard-interactive/pam, sudo command audit lines, sudo password failures, sudoers policy denials, su success/failure audit lines, and selected PAM session/auth lines.
Recognized success or audit families include accepted password, accepted publickey, accepted keyboard-interactive/pam, sudo command audit lines, sudo password failures, sudoers policy denials, su success/failure audit lines, selected util-linux `login` failures and session records, and selected PAM session/auth lines. `login` failures do not infer a network source IP and remain lower-confidence `pam_auth_failure` context.

The selected `login` wording is anchored to the upstream
[`login.c`](https://github.com/util-linux/util-linux/blob/9447bc4f72ac5634dcfd78ea877286279b050369/login-utils/login.c)
syslog strings. Localized or otherwise unmodeled variants remain visible as
`login_other` warnings.

## Line handling contract

Expand Down Expand Up @@ -115,7 +121,7 @@ Parsed successes and audit-only events remain reportable but do not count as bru
| [`assets/parser_auth_families_syslog.log`](../assets/parser_auth_families_syslog.log) | Syslog PAM/auth-family parser coverage |
| [`assets/parser_auth_families_journalctl_short_full.log`](../assets/parser_auth_families_journalctl_short_full.log) | Journalctl PAM/auth-family parser coverage |
| [`assets/noisy_auth_sample.log`](../assets/noisy_auth_sample.log) and [`tests/fixtures/parser_matrix/noisy_auth_expected.json`](../tests/fixtures/parser_matrix/noisy_auth_expected.json) | Noisy syslog parser-coverage matrix for malformed, unsupported, blank, irrelevant, multi-host, and unusual-username input |
| [`assets/mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) and [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) | 150-line mixed auth corpus plus reviewer-facing parser coverage artifact for dirty syslog input |
| [`assets/mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) and [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) | 160-line mixed auth corpus plus reviewer-facing parser coverage artifact for dirty syslog input and selected util-linux `login` evidence |
| [`tests/test_report_contracts.cpp`](../tests/test_report_contracts.cpp) | Stable report-shape expectations for generated artifacts |

## Non-goals
Expand Down
10 changes: 5 additions & 5 deletions docs/parser-coverage-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ The locked expected coverage summary lives in [`tests/fixtures/parser_matrix/noi

## Mixed auth corpus

[`assets/mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) is a 150-line sanitized `syslog_legacy` corpus for dirty-input review. It mixes Ubuntu / Debian `auth.log`-style and RHEL-family `secure`-style host labels while keeping the same BSD syslog header contract. This is a parser-observability fixture, not a claim of complete distro coverage.
[`assets/mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) is a 160-line sanitized `syslog_legacy` corpus for dirty-input review. It mixes Ubuntu / Debian `auth.log`-style and RHEL-family `secure`-style host labels while keeping the same BSD syslog header contract. This is a parser-observability fixture, not a claim of complete distro coverage.

The corpus repeats ten small evidence batches. Each batch includes recognized `sshd`, `sudo`, `su`, `pam_unix`, `pam_faillock`, and `pam_sss` evidence; unsupported `sshd` preauth and `pam_unix` session-close telemetry; an unsupported service program; a malformed source IP; an invalid timestamp; and one blank line.
The corpus repeats ten small evidence batches. Each batch includes recognized `sshd`, `sudo`, `su`, `pam_unix`, `pam_faillock`, and `pam_sss` evidence; unsupported `sshd` preauth and `pam_unix` session-close telemetry; an unsupported service program; a malformed source IP; an invalid timestamp; and one blank line. A final ten-line segment adds selected util-linux `login` failure and session records without changing the warning corpus.

For reviewer inspection without running the test suite, [`assets/mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json) captures the deterministic parser coverage view for this corpus: parser-quality counters, normalized event-type counts, unknown-pattern buckets, failure categories, and warning line references.

Locked parser expectations:

- `total_input_lines`: 150
- `total_input_lines`: 160
- `skipped_blank_lines`: 10
- `parsed_lines`: 90
- `parsed_lines`: 100
- `unparsed_lines`: 50
- normalized event counts: 10 invalid-user SSH failures, 10 failed-publickey SSH events, 10 accepted-publickey SSH events, 10 sudo command events, 10 sudo auth failures, 30 PAM auth failures, and 10 `su` auth failures
- normalized event counts: 10 invalid-user SSH failures, 10 failed-publickey SSH events, 10 accepted-publickey SSH events, 10 sudo command events, 10 sudo auth failures, 36 PAM auth failures, 4 session-opened events, and 10 `su` auth failures
- `failure_categories`: 10 each for `known_program_unknown_message`, `malformed_source_ip`, `unknown_program`, `unknown_timestamp`, and `unsupported_pam_variant`
- `top_unknown_patterns`: 10 each for `invalid_month_token`, `malformed_source_ip`, `pam_unix_session_closed`, `program_cron`, and `sshd_connection_closed_preauth`

Expand Down
5 changes: 5 additions & 0 deletions src/parser/failure_classifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ std::string classify_unknown_auth_pattern(const Event& event) {
return "su_other";
}

if (event.program == "login") {
return "login_other";
}

return "program_" + sanitize_pattern_label(event.program);
}

Expand All @@ -194,6 +198,7 @@ bool is_known_auth_program(std::string_view program) {
return program == "sshd"
|| program == "sudo"
|| program == "su"
|| program == "login"
|| is_pam_program(program);
}

Expand Down
Loading
Loading