Skip to content

Commit afca612

Browse files
authored
Merge pull request #94 from stacknil/stacknil/login-handler-corpus
feat(parser): add util-linux login handler
2 parents e0cc39a + f769542 commit afca612

16 files changed

Lines changed: 320 additions & 26 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ All notable user-visible changes should be recorded here.
1919
result invariants.
2020
- Added an optional Clang libFuzzer parser target with a sanitized seed corpus
2121
and bounded Ubuntu CI smoke campaign.
22+
- Added a dedicated util-linux `login` handler for selected failed-login,
23+
retry-exhaustion, session-failure, local-login, and root-login messages.
24+
- Expanded the sanitized mixed auth corpus from 150 to 160 lines with ten
25+
fixture-backed `login` records and updated parser coverage telemetry.
2226

2327
### Changed
2428

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(LOGLENS_LIBRARY_SOURCES
1212
src/config.cpp
1313
src/parser.cpp
1414
src/parser/failure_classifier.cpp
15+
src/parser/login_handlers.cpp
1516
src/parser/pam_handlers.cpp
1617
src/parser/program_dispatch.cpp
1718
src/parser/source_envelope_parser.cpp

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ LogLens currently parses and reports these additional auth patterns beyond the c
102102
- `sshd`-owned `PAM: Authentication failure ...` lines, including OpenSSH's optional leading `error:` marker, with invalid/illegal-user variants normalized to `ssh_invalid_user`
103103
- `sudo` command, password-failure, and sudoers policy-denial audit lines
104104
- `su` success and failure audit lines
105+
- selected util-linux `login` failure, retry-exhaustion, session-failure,
106+
local-login, and root-login audit lines
105107
- `pam_unix(...:auth): authentication failure`
106108
- `pam_unix(...:session): session opened`
107109
- selected `pam_faillock(...:auth)` failure variants

assets/mixed_auth_corpus.log

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,13 @@ Mar 12 08:09:45 mixed-auth-10 cron[5008]: (root) CMD (/usr/bin/true)
148148
Mar 12 08:09:49 mixed-auth-10 sshd[5009]: Failed password for invalid user user010 from 999.0.113.106 port 51905 ssh2
149149

150150
Foo 12 08:09:53 mixed-auth-10 sshd[5010]: Failed password for invalid user user010 from 203.0.113.107 port 51906 ssh2
151+
Mar 12 08:10:01 mixed-auth-11 login[5101]: FAILED LOGIN 1 FROM tty1 FOR user011, Authentication failure
152+
Mar 12 08:10:05 mixed-auth-11 login[5102]: TOO MANY LOGIN TRIES (3) FROM tty1 FOR user011, Authentication failure
153+
Mar 12 08:10:09 mixed-auth-11 login[5103]: FAILED LOGIN SESSION FROM tty1 FOR user011, Authentication failure
154+
Mar 12 08:10:13 mixed-auth-11 login[5104]: LOGIN ON tty1 BY user011
155+
Mar 12 08:10:17 mixed-auth-11 login[5105]: ROOT LOGIN ON tty2
156+
Mar 12 08:10:21 mixed-auth-12 login[5201]: FAILED LOGIN 1 FROM tty3 FOR user012, Authentication failure
157+
Mar 12 08:10:25 mixed-auth-12 login[5202]: TOO MANY LOGIN TRIES (3) FROM tty3 FOR user012, Authentication failure
158+
Mar 12 08:10:29 mixed-auth-12 login[5203]: FAILED LOGIN SESSION FROM tty3 FOR user012, Authentication failure
159+
Mar 12 08:10:33 mixed-auth-12 login[5204]: LOGIN ON tty3 BY user012 FROM example-console
160+
Mar 12 08:10:37 mixed-auth-13 login[5301]: LOGIN ON tty4 BY user013

assets/mixed_auth_parser_coverage.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
"input_mode": "syslog_legacy",
66
"assume_year": 2026,
77
"parser_quality": {
8-
"total_input_lines": 150,
9-
"total_lines": 140,
8+
"total_input_lines": 160,
9+
"total_lines": 150,
1010
"skipped_blank_lines": 10,
11-
"parsed_lines": 90,
11+
"parsed_lines": 100,
1212
"unparsed_lines": 50,
13-
"parse_success_rate": 0.6428571429,
13+
"parse_success_rate": 0.6666666667,
1414
"top_unknown_patterns": [
1515
{"pattern": "invalid_month_token", "count": 10},
1616
{"pattern": "malformed_source_ip", "count": 10},
@@ -26,13 +26,14 @@
2626
{"category": "unsupported_pam_variant", "count": 10}
2727
]
2828
},
29-
"parsed_event_count": 90,
29+
"parsed_event_count": 100,
3030
"warning_count": 50,
3131
"event_type_counts": [
3232
{"event_type": "ssh_accepted_publickey", "count": 10},
3333
{"event_type": "ssh_invalid_user", "count": 10},
3434
{"event_type": "ssh_failed_publickey", "count": 10},
35-
{"event_type": "pam_auth_failure", "count": 30},
35+
{"event_type": "pam_auth_failure", "count": 36},
36+
{"event_type": "session_opened", "count": 4},
3637
{"event_type": "sudo_command", "count": 10},
3738
{"event_type": "sudo_auth_failure", "count": 10},
3839
{"event_type": "su_auth_failure", "count": 10}

docs/case-study-parser-uncertainty-as-evidence.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ the narrower engineering question.
3333

3434
The checked-in
3535
[`mixed_auth_corpus.log`](../assets/mixed_auth_corpus.log) is a sanitized,
36-
150-line syslog-style fixture. Its paired
36+
160-line syslog-style fixture. Its paired
3737
[`mixed_auth_parser_coverage.json`](../assets/mixed_auth_parser_coverage.json)
3838
records recognized events, warnings, blank lines, failure categories, pattern
3939
buckets, and source-line references.

docs/parser-conformance-matrix.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ coverage of every distro or PAM module wording.
4747
| `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` |
4848
| `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 |
4949
| `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 |
50+
| `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 |
5051
| `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 |
5152
| `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 |
5253
| `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 |
@@ -80,6 +81,8 @@ coverage of every distro or PAM module wording.
8081
| 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. |
8182
| `su` failure audit | `FAILED SU (to <target>) <actor> on <tty>` | `syslog_legacy`, `journalctl_short_full` | `su_auth_failure` | Normalized `username` is the actor. |
8283
| `su` success audit | `Successful su for <target> by <actor>` | `syslog_legacy`, `journalctl_short_full` | `session_opened` | Normalized `username` is the actor. |
84+
| 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. |
85+
| 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. |
8386

8487
## Unsupported Bucket Matrix
8588

@@ -102,6 +105,7 @@ normalized event is always `none`.
102105
| Other unsupported `pam_sss(...)` messages | `syslog_legacy`, `journalctl_short_full` | `unsupported_pam_variant` | `pam_sss_other` | none |
103106
| 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 |
104107
| 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 |
108+
| Well-formed `login` line outside the selected util-linux message shapes | `syslog_legacy`, `journalctl_short_full` | `known_program_unknown_message` | `login_other` | none |
105109
| Well-formed unsupported program tag | `syslog_legacy`, `journalctl_short_full` | `unknown_program` | `program_<sanitized_program>` | none |
106110

107111
## Header And Structural Warning Matrix
@@ -133,7 +137,7 @@ coverage telemetry path.
133137
| [`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 |
134138
| [`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 |
135139
| [`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 |
136-
| [`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 |
140+
| [`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 |
137141

138142
## Review Rule
139143

docs/parser-contract.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@ The parser currently recognizes common authentication evidence from:
2828
- `sshd`
2929
- `sudo`
3030
- `su`
31+
- `login`
3132
- `pam_unix(...)`
3233
- selected `pam_faillock(...)` variants
3334
- selected `pam_sss(...)` variants
3435

3536
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.
3637

37-
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.
38+
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.
39+
40+
The selected `login` wording is anchored to the upstream
41+
[`login.c`](https://github.com/util-linux/util-linux/blob/9447bc4f72ac5634dcfd78ea877286279b050369/login-utils/login.c)
42+
syslog strings. Localized or otherwise unmodeled variants remain visible as
43+
`login_other` warnings.
3844

3945
## Line handling contract
4046

@@ -115,7 +121,7 @@ Parsed successes and audit-only events remain reportable but do not count as bru
115121
| [`assets/parser_auth_families_syslog.log`](../assets/parser_auth_families_syslog.log) | Syslog PAM/auth-family parser coverage |
116122
| [`assets/parser_auth_families_journalctl_short_full.log`](../assets/parser_auth_families_journalctl_short_full.log) | Journalctl PAM/auth-family parser coverage |
117123
| [`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 |
118-
| [`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 |
124+
| [`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 |
119125
| [`tests/test_report_contracts.cpp`](../tests/test_report_contracts.cpp) | Stable report-shape expectations for generated artifacts |
120126

121127
## Non-goals

docs/parser-coverage-notes.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ The locked expected coverage summary lives in [`tests/fixtures/parser_matrix/noi
2525

2626
## Mixed auth corpus
2727

28-
[`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.
28+
[`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.
2929

30-
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.
30+
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.
3131

3232
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.
3333

3434
Locked parser expectations:
3535

36-
- `total_input_lines`: 150
36+
- `total_input_lines`: 160
3737
- `skipped_blank_lines`: 10
38-
- `parsed_lines`: 90
38+
- `parsed_lines`: 100
3939
- `unparsed_lines`: 50
40-
- 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
40+
- 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
4141
- `failure_categories`: 10 each for `known_program_unknown_message`, `malformed_source_ip`, `unknown_program`, `unknown_timestamp`, and `unsupported_pam_variant`
4242
- `top_unknown_patterns`: 10 each for `invalid_month_token`, `malformed_source_ip`, `pam_unix_session_closed`, `program_cron`, and `sshd_connection_closed_preauth`
4343

src/parser/failure_classifier.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ std::string classify_unknown_auth_pattern(const Event& event) {
181181
return "su_other";
182182
}
183183

184+
if (event.program == "login") {
185+
return "login_other";
186+
}
187+
184188
return "program_" + sanitize_pattern_label(event.program);
185189
}
186190

@@ -194,6 +198,7 @@ bool is_known_auth_program(std::string_view program) {
194198
return program == "sshd"
195199
|| program == "sudo"
196200
|| program == "su"
201+
|| program == "login"
197202
|| is_pam_program(program);
198203
}
199204

0 commit comments

Comments
 (0)