istoreos/package/network/services/dropbear/patches/003-Retain-utmp-saved-group-when-dropping-privileges.patch
Hauke Mehrtens 0f52a05723 dropbear: backport security fixes
This fixes the following security problems:
CVE-2025-14282: Avoid privilege escalation via unix stream forwarding in Dropbear server.
CVE-2019-6111: This allowed a malicious server to overwrite arbitrary local files.

This backports two upstream merged PRs:
https://github.com/mkj/dropbear/pull/391
https://github.com/mkj/dropbear/pull/394
and this upstream commit:
48a17cff6a

Link: https://github.com/openwrt/openwrt/pull/21192
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2025-12-17 21:19:28 +01:00

261 lines
7.1 KiB
Diff

From b20e7823a0c6a4480aa473e0a46d3ccab4b9b102 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Tue, 9 Dec 2025 09:05:30 +0900
Subject: Retain utmp saved group when dropping privileges
utmp is required to record logout. The saved group
is reset by the OS for the executed user shell.
This requires setresgid() function which is not available on all
platforms. Notable platforms are netbsd and macos. Those platforms will
have to set DROPBEAR_SVR_DROP_PRIVS 0 unless an alternative approach is
found.
(cherry picked from commit 73e4e70ea8e6b890c3918b52bb2e647313a09faa)
---
.github/workflows/build.yml | 6 ++++
configure | 7 +++++
configure.ac | 1 +
src/auth.h | 2 ++
src/config.h.in | 3 ++
src/loginrec.c | 6 ----
src/session.h | 6 ++++
src/svr-auth.c | 61 +++++++++++++++++++++++++++++++++++--
src/svr-chansession.c | 8 +++++
src/sysoptions.h | 4 +++
10 files changed, 96 insertions(+), 8 deletions(-)
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -72,6 +72,9 @@ jobs:
# fails with:
# .../ranlib: file: libtomcrypt.a(cbc_setiv.o) has no symbols
ranlib: ranlib -no_warning_for_no_symbols
+ # macos doesn't have setresgid
+ localoptions: |
+ #define DROPBEAR_SVR_DROP_PRIVS 0
- name: macos 15
os: macos-15
@@ -84,6 +87,9 @@ jobs:
# fails with:
# .../ranlib: file: libtomcrypt.a(cbc_setiv.o) has no symbols
ranlib: ranlib -no_warning_for_no_symbols
+ # macos doesn't have setresgid
+ localoptions: |
+ #define DROPBEAR_SVR_DROP_PRIVS 0
# Check that debug code doesn't bitrot
- name: DEBUG_TRACE
--- a/configure
+++ b/configure
@@ -7597,6 +7597,13 @@ then :
fi
+ac_fn_c_check_func "$LINENO" "setresgid" "ac_cv_func_setresgid"
+if test "x$ac_cv_func_setresgid" = xyes
+then :
+ printf "%s\n" "#define HAVE_SETRESGID 1" >>confdefs.h
+
+fi
+
# POSIX monotonic time
ac_fn_c_check_func "$LINENO" "clock_gettime" "ac_cv_func_clock_gettime"
--- a/configure.ac
+++ b/configure.ac
@@ -545,6 +545,7 @@ AC_CHECK_FUNCS(utmpname)
AC_CHECK_FUNCS(endutxent getutxent getutxid getutxline pututxline )
AC_CHECK_FUNCS(setutxent utmpxname)
AC_CHECK_FUNCS(logout updwtmp logwtmp)
+AC_CHECK_FUNCS(setresgid)
# POSIX monotonic time
AC_CHECK_FUNCS(clock_gettime)
--- a/src/auth.h
+++ b/src/auth.h
@@ -41,6 +41,8 @@ void svr_auth_password(int valid_user);
void svr_auth_pubkey(int valid_user);
void svr_auth_pam(int valid_user);
void svr_switch_user(void);
+void svr_raise_gid_utmp(void);
+void svr_restore_gid(void);
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
int svr_pubkey_allows_agentfwd(void);
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -222,6 +222,9 @@
/* Define to 1 if you have the <security/pam_appl.h> header file. */
#undef HAVE_SECURITY_PAM_APPL_H
+/* Define to 1 if you have the `setresgid' function. */
+#undef HAVE_SETRESGID
+
/* Define to 1 if you have the `setutent' function. */
#undef HAVE_SETUTENT
--- a/src/loginrec.c
+++ b/src/loginrec.c
@@ -304,12 +304,6 @@ login_set_current_time(struct logininfo
void
login_write (struct logininfo *li)
{
-#ifndef HAVE_CYGWIN
- if ((int)geteuid() != 0) {
- return 1;
- }
-#endif
-
/* set the timestamp */
login_set_current_time(li);
#ifdef USE_LOGIN
--- a/src/session.h
+++ b/src/session.h
@@ -271,6 +271,12 @@ struct serversession {
/* The instance created by the plugin_new function */
struct PluginInstance *plugin_instance;
#endif
+
+#if DROPBEAR_SVR_DROP_PRIVS
+ /* Set to 1 when utmp_gid is valid */
+ int have_utmp_gid;
+ gid_t utmp_gid;
+#endif
};
typedef enum {
--- a/src/svr-auth.c
+++ b/src/svr-auth.c
@@ -457,13 +457,14 @@ void send_msg_userauth_success() {
* delayed-zlib mode */
ses.authstate.authdone = 1;
-#if DROPBEAR_DROP_PRIVS
+#if DROPBEAR_SVR_DROP_PRIVS
+ /* Drop privileges as soon as authentication has happened. */
svr_switch_user();
#endif
ses.connect_time = 0;
-#if DROPBEAR_DROP_PRIVS
+#if DROPBEAR_SVR_DROP_PRIVS
/* If running as the user, we can rely on the OS
* to limit allowed ports */
ses.allowprivport = 1;
@@ -482,6 +483,20 @@ void send_msg_userauth_success() {
}
+#if DROPBEAR_SVR_DROP_PRIVS
+/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
+static int utmp_gid(gid_t *ret_gid) {
+ struct group *utmp_gr = getgrnam("utmp");
+ if (!utmp_gr) {
+ TRACE(("No utmp group"));
+ return DROPBEAR_FAILURE;
+ }
+
+ *ret_gid = utmp_gr->gr_gid;
+ return DROPBEAR_SUCCESS;
+}
+#endif
+
/* Switch to the ses.authstate user.
* Fails if not running as root and the user differs.
*
@@ -499,6 +514,25 @@ void svr_switch_user(void) {
ses.authstate.pw_gid) < 0)) {
dropbear_exit("Error changing user group");
}
+
+#if DROPBEAR_SVR_DROP_PRIVS
+ /* Retain utmp saved group so that wtmp/utmp can be written */
+ int ret = utmp_gid(&svr_ses.utmp_gid);
+ if (ret == DROPBEAR_SUCCESS) {
+ /* Set saved gid to utmp so that it can be
+ * restored for login_logout() etc. This saved
+ * group is cleared by the OS on execve() */
+ int rc = setresgid(-1, -1, svr_ses.utmp_gid);
+ if (rc == 0) {
+ svr_ses.have_utmp_gid = 1;
+ } else {
+ /* Will not attempt to switch to utmp gid.
+ * login() etc may fail. */
+ TRACE(("utmp setresgid failed"));
+ }
+ }
+#endif
+
if (setuid(ses.authstate.pw_uid) < 0) {
dropbear_exit("Error changing user");
}
@@ -516,3 +550,26 @@ void svr_switch_user(void) {
}
}
+void svr_raise_gid_utmp(void) {
+#if DROPBEAR_SVR_DROP_PRIVS
+ if (!svr_ses.have_utmp_gid) {
+ return;
+ }
+
+ if (setegid(svr_ses.utmp_gid) != 0) {
+ dropbear_log(LOG_WARNING, "failed setegid");
+ }
+#endif
+}
+
+void svr_restore_gid(void) {
+#if DROPBEAR_SVR_DROP_PRIVS
+ if (!svr_ses.have_utmp_gid) {
+ return;
+ }
+
+ if (setegid(getgid()) != 0) {
+ dropbear_log(LOG_WARNING, "failed setegid");
+ }
+#endif
+}
--- a/src/svr-chansession.c
+++ b/src/svr-chansession.c
@@ -326,7 +326,11 @@ static void cleanupchansess(const struct
if (chansess->tty) {
/* write the utmp/wtmp login record */
li = chansess_login_alloc(chansess);
+
+ svr_raise_gid_utmp();
login_logout(li);
+ svr_restore_gid();
+
login_free_entry(li);
pty_release(chansess->tty);
@@ -847,7 +851,11 @@ static int ptycommand(struct Channel *ch
* terminal used for stdout with the dup2 above, otherwise
* the wtmp login will not be recorded */
li = chansess_login_alloc(chansess);
+
+ svr_raise_gid_utmp();
login_login(li);
+ svr_restore_gid();
+
login_free_entry(li);
/* Can now dup2 stderr. Messages from login_login() have gone
--- a/src/sysoptions.h
+++ b/src/sysoptions.h
@@ -318,6 +318,10 @@
#error "At least one hostkey or public-key algorithm must be enabled; RSA is recommended."
#endif
+#if DROPBEAR_SVR_DROP_PRIVS && !defined(HAVE_SETRESGID)
+ #error "DROPBEAR_SVR_DROP_PRIVS requires setresgid()."
+#endif
+
/* Source for randomness. This must be able to provide hundreds of bytes per SSH
* connection without blocking. */
#ifndef DROPBEAR_URANDOM_DEV