Sync from SUSE:ALP:Source:Standard:1.0 cockpit revision ceae2a1bfb3a5166c79a2adc4ce3fc44

This commit is contained in:
Adrian Schröter 2024-08-15 16:36:50 +02:00
parent 9e567cd89d
commit 4b95db9a69
4 changed files with 308 additions and 1 deletions

View File

@ -0,0 +1,137 @@
From b95268ea4002fe7bcc7839d0bc7587c4e76306c5 Mon Sep 17 00:00:00 2001
From: Luna <luna.dragon@suse.com>
Date: Wed, 5 Jun 2024 10:34:15 +0530
Subject: [PATCH] users: Support for watching lastlog2 and wutmp on overview
page
---
pkg/users/account-details.js | 27 ++++++++++++++++++---------
pkg/users/users.js | 22 ++++++++++++++--------
pkg/users/utils.js | 14 ++++++++++++++
3 files changed, 46 insertions(+), 17 deletions(-)
diff --git a/pkg/users/account-details.js b/pkg/users/account-details.js
index 9edd70c41..94710912a 100644
--- a/pkg/users/account-details.js
+++ b/pkg/users/account-details.js
@@ -48,7 +48,8 @@ import { account_shell_dialog } from "./shell-dialog.js";
import { set_password_dialog, reset_password_dialog } from "./password-dialogs.js";
import { AccountLogs } from "./account-logs-panel.jsx";
import { AuthorizedKeys } from "./authorized-keys-panel.js";
-import { get_locked } from "./utils.js";
+import { get_locked, getUtmpPath } from "./utils.js";
+import { useInit } from 'hooks.js';
const _ = cockpit.gettext;
@@ -98,16 +99,24 @@ function get_expire(name) {
export function AccountDetails({ accounts, groups, current_user, user, shells }) {
const [expiration, setExpiration] = useState(null);
- useEffect(() => {
- get_expire(user).then(setExpiration);
+ const [utmppath, setUtmpPath] = useState(null);
+
+ useInit(async () => {
+ setUtmpPath(await getUtmpPath());
+ });
- // Watch `/var/run/utmp` to register when user logs in or out
- const handle = cockpit.file("/var/run/utmp", { superuser: "try", binary: true });
- handle.watch(() => {
+ useEffect(() => {
+ if (utmppath !== null) {
get_expire(user).then(setExpiration);
- });
- return handle.close;
- }, [user, accounts]);
+
+ // Watch `/var/run/utmp` to register when user logs in or out
+ const handle = cockpit.file("/var/run/utmp", { superuser: "try", binary: true });
+ handle.watch(() => {
+ get_expire(user).then(setExpiration);
+ });
+ return handle.close;
+ }
+ }, [user, accounts, utmppath]);
const [edited_real_name, set_edited_real_name] = useState(null);
const [committing_real_name, set_committing_real_name] = useState(false);
diff --git a/pkg/users/users.js b/pkg/users/users.js
index 8f35be519..ecafc3472 100755
--- a/pkg/users/users.js
+++ b/pkg/users/users.js
@@ -29,7 +29,7 @@ import { usePageLocation, useLoggedInUser, useFile, useInit } from "hooks.js";
import { etc_passwd_syntax, etc_group_syntax, etc_shells_syntax } from "pam_user_parser.js";
import { EmptyStatePanel } from "cockpit-components-empty-state.jsx";
-import { get_locked } from "./utils.js";
+import { get_locked, getUtmpPath } from "./utils.js";
import { AccountsMain } from "./accounts-list.js";
import { AccountDetails } from "./account-details.js";
@@ -86,13 +86,17 @@ function AccountsPage() {
}, [logindef]);
const [details, setDetails] = useState(null);
- useInit(() => {
+ useInit(async () => {
+ const utmppath = await getUtmpPath();
+
getLogins().then(setDetails);
// Watch `/var/run/utmp` to register when user logs in or out
- const handleUtmp = cockpit.file("/var/run/utmp", { superuser: "try", binary: true });
- handleUtmp.watch(() => getLogins().then(setDetails), { read: false });
-
+ let handleUtmp;
+ if (utmppath !== null) {
+ handleUtmp = cockpit.file("/var/run/utmp", { superuser: "try", binary: true });
+ handleUtmp.watch(() => getLogins().then(setDetails), { read: false });
+ }
// Watch /etc/shadow to register lock/unlock/expire changes; but avoid reading it, it's sensitive data
const handleShadow = cockpit.file("/etc/shadow", { superuser: "try" });
handleShadow.watch(() => getLogins().then(setDetails), { read: false });
@@ -151,12 +155,14 @@ function AccountsPage() {
}
async function getLogins() {
- let lastlog = "";
+ let LastLogPath;
try {
- lastlog = await cockpit.spawn(["lastlog"], { environ: ["LC_ALL=C"] });
+ await cockpit.spawn(["test", "-e", "/var/lib/lastlog/lastlog2.db"], { err: "ignore" });
+ LastLogPath = "lastlog2";
} catch (err) {
- console.warn("Unexpected error when getting last login information", err);
+ LastLogPath = "lastlog";
}
+ const lastlog = await cockpit.spawn([LastLogPath], { environ: ["LC_ALL=C"] });
let currentLogins = [];
try {
diff --git a/pkg/users/utils.js b/pkg/users/utils.js
index 7b2efed05..3cbe295a5 100644
--- a/pkg/users/utils.js
+++ b/pkg/users/utils.js
@@ -8,3 +8,17 @@ export const get_locked = name =>
return status == "LK" || status == "L";
})
.catch(() => null);
+
+export async function getUtmpPath() {
+ try {
+ await cockpit.spawn(["test", "-e", "/var/run/utmp"], { err: "ignore" });
+ return "/var/run/utmp";
+ } catch (err1) {
+ try {
+ await cockpit.spawn(["test", "-e", "/var/lib/wtmpdb/wtmp.db"], { err: "ignore" });
+ return "/var/lib/wtmpdb/wtmp.db";
+ } catch (err2) {
+ return null;
+ }
+ }
+}
--
2.45.1

156
CVE-2024-6126.patch Normal file
View File

@ -0,0 +1,156 @@
From 2274359df6feffc990831c7d7a32a56d9244d38a Mon Sep 17 00:00:00 2001
From: Martin Pitt <mpitt@redhat.com>
Date: Mon, 10 Jun 2024 10:49:56 +0200
Subject: [PATCH] pam-ssh-add: Fix insecure killing of session ssh-agent
[CVE-2024-6126]
Some distributions like Debian 12, or possibly some administrators
enable pam_env's deprecated `user_readenv` option [1]. The user session
can change the `$SSH_AGENT_PID`, so that it can pass an arbitrary pid to
`pam_sm_close_session()`. This is a local authenticated DoS.
Avoid this by storing the agent pid in a global variable. The
cockpit-session process stays around for the entire session time, so we
don't need to put the pid into the PAM data.
It can also happen that the user session's ssh-agent gets killed, and
some other process later on recycles the PID. Temporarily drop
privileges to the target user so that we at least don't kill anyone
else's process.
Add an integration test which checks that changing the env variable
works, pointing it to a different process doesn't kill that, and
ssh-agent (the original pid) is still cleaned up correctly. However, as
pam_so.env in Fedora crashes hard, skip the test there.
Many thanks to Paolo Perego <paolo.perego@suse.com> for discovering,
and Luna Dragon <luna.dragon@suse.com> for reporting this issue!
[1] https://man7.org/linux/man-pages/man8/pam_env.8.html
CVE-2024-6126
https://bugzilla.redhat.com/show_bug.cgi?id=2290859
---
src/pam-ssh-add/pam-ssh-add.c | 46 ++++++++++++++++++++++++++++-------
test/verify/check-session | 30 +++++++++++++++++++++++
2 files changed, 67 insertions(+), 9 deletions(-)
diff --git a/src/pam-ssh-add/pam-ssh-add.c b/src/pam-ssh-add/pam-ssh-add.c
index a9159d710..839b797d2 100644
--- a/src/pam-ssh-add/pam-ssh-add.c
+++ b/src/pam-ssh-add/pam-ssh-add.c
@@ -54,6 +54,9 @@ const char *pam_ssh_agent_arg = NULL;
const char *pam_ssh_add_program = PATH_SSH_ADD;
const char *pam_ssh_add_arg = NULL;
+static unsigned long ssh_agent_pid;
+static uid_t ssh_agent_uid;
+
/* Environment */
#define ENVIRON_SIZE 5
#define PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
@@ -866,6 +869,25 @@ start_agent (pam_handle_t *pamh,
error ("couldn't set agent environment: %s",
pam_strerror (pamh, res));
}
+
+ /* parse and store the agent pid for later cleanup */
+ if (strncmp (auth_pid, "SSH_AGENT_PID=", 14) == 0)
+ {
+ unsigned long pid = strtoul (auth_pid + 14, NULL, 10);
+ if (pid > 0 && pid != ULONG_MAX)
+ {
+ ssh_agent_pid = pid;
+ ssh_agent_uid = auth_pwd->pw_uid;
+ }
+ else
+ {
+ error ("invalid SSH_AGENT_PID value: %s", auth_pid);
+ }
+ }
+ else
+ {
+ error ("unexpected agent pid format: %s", auth_pid);
+ }
}
free (auth_socket);
@@ -952,19 +974,25 @@ pam_sm_close_session (pam_handle_t *pamh,
int argc,
const char *argv[])
{
- const char *s_pid;
- int pid = 0;
parse_args (argc, argv);
/* Kill the ssh agent we started */
- s_pid = pam_getenv (pamh, "SSH_AGENT_PID");
- if (s_pid)
- pid = atoi (s_pid);
-
- if (pid > 0)
+ if (ssh_agent_pid > 0)
{
- debug ("Closing %d", pid);
- kill (pid, SIGTERM);
+ debug ("Closing %lu", ssh_agent_pid);
+ /* kill as user to guard against crashing ssh-agent and PID reuse */
+ if (setresuid (ssh_agent_uid, ssh_agent_uid, -1) < 0)
+ {
+ error ("could not drop privileges for killing ssh agent: %m");
+ return PAM_SESSION_ERR;
+ }
+ if (kill (ssh_agent_pid, SIGTERM) < 0 && errno != ESRCH)
+ message ("could not kill ssh agent %lu: %m", ssh_agent_pid);
+ if (setresuid (0, 0, -1) < 0)
+ {
+ error ("could not restore privileges after killing ssh agent: %m");
+ return PAM_SESSION_ERR;
+ }
}
return PAM_SUCCESS;
}
diff --git a/test/verify/check-session b/test/verify/check-session
index f771b5f69..939d29428 100755
--- a/test/verify/check-session
+++ b/test/verify/check-session
@@ -76,6 +76,36 @@ class TestSession(testlib.MachineCase):
b.logout()
wait_session(should_exist=False)
+ # try to pwn $SSH_AGENT_PID via pam_env's user_readenv=1
+
+ if m.image in ["fedora-39", "fedora-40"]:
+ # pam_env user_readenv crashes in Fedora, skip the test
+ # https://bugzilla.redhat.com/show_bug.cgi?id=2293045
+ return
+
+ # this is enabled by default in tools/cockpit.debian.pam, as well as
+ # Debian/Ubuntu's /etc/pam.d/sshd; but not in Fedora/RHEL
+ if "debian" not in m.image and "ubuntu" not in m.image:
+ self.write_file("/etc/pam.d/cockpit", "session required pam_env.so user_readenv=1\n", append=True)
+ victim_pid = m.spawn("sleep infinity", "sleep.log")
+ self.addCleanup(m.execute, f"kill {victim_pid} || true")
+ self.write_file("/home/admin/.pam_environment", f"SSH_AGENT_PID={victim_pid}\n", owner="admin")
+
+ b.login_and_go()
+ wait_session(should_exist=True)
+ # starts ssh-agent in session
+ m.execute("pgrep -u admin ssh-agent")
+ # but the session has the modified SSH_AGENT_PID
+ bridge = m.execute("pgrep -u admin cockpit-bridge").strip()
+ agent = m.execute(f"grep --null-data SSH_AGENT_PID /proc/{bridge}/environ | xargs -0 | sed 's/.*=//'").strip()
+ self.assertEqual(agent, str(victim_pid))
+
+ # logging out still kills the actual ssh-agent, not the victim pid
+ b.logout()
+ wait_session(should_exist=False)
+ m.execute("while pgrep -u admin ssh-agent; do sleep 1; done", timeout=10)
+ m.execute(f"test -e /proc/{victim_pid}")
+
if __name__ == '__main__':
testlib.test_main()
--
2.45.2

View File

@ -1,3 +1,13 @@
-------------------------------------------------------------------
Wed Jul 3 06:04:40 UTC 2024 - Luna D Dragon <luna.dragon@suse.com>
- add CVE-2024-6126.patch to resolve CVE-2024-6126
-------------------------------------------------------------------
Wed Jun 5 05:11:19 UTC 2024 - Luna D Dragon <luna.dragon@suse.com>
- add 0001-users-Support-for-watching-lastlog2-and-wutmp-on-ove.patch to fix bsc#1220551
------------------------------------------------------------------- -------------------------------------------------------------------
Wed Mar 13 11:26:28 UTC 2024 - Miika Alikirri <miika.alikirri@suse.com> Wed Mar 13 11:26:28 UTC 2024 - Miika Alikirri <miika.alikirri@suse.com>

View File

@ -66,11 +66,13 @@ Patch2: suse_docs.patch
Patch3: suse-microos-branding.patch Patch3: suse-microos-branding.patch
Patch4: css-overrides.patch Patch4: css-overrides.patch
Patch5: storage-btrfs.patch Patch5: storage-btrfs.patch
Patch6: 0001-users-Support-for-watching-lastlog2-and-wutmp-on-ove.patch
Patch7: CVE-2024-6126.patch
# SLE Micro specific patches # SLE Micro specific patches
Patch101: hide-pcp.patch Patch101: hide-pcp.patch
Patch102: 0002-selinux-temporary-remove-setroubleshoot-section.patch Patch102: 0002-selinux-temporary-remove-setroubleshoot-section.patch
# For anything based on SLES 15 codebase (including Leap, SLE Micro) # For anything based on SLES 15 codebase (including Leap, SLE Micro)
Patch103: 0004-leap-gnu18-removal.patch #Patch103: 0004-leap-gnu18-removal.patch
Patch104: selinux_libdir.patch Patch104: selinux_libdir.patch
%if 0%{?fedora} >= 38 || 0%{?rhel} >= 9 %if 0%{?fedora} >= 38 || 0%{?rhel} >= 9
@ -247,6 +249,8 @@ BuildRequires: python3-tox-current-env
%patch3 -p1 %patch3 -p1
%patch4 -p1 %patch4 -p1
%patch5 -p1 %patch5 -p1
%patch6 -p1
%patch7 -p1
# SLE Micro specific patches # SLE Micro specific patches
%if 0%{?is_smo} %if 0%{?is_smo}