Version in base suite: 2025.88-2 Base version: dropbear_2025.88-2 Target version: dropbear_2025.89-1~deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/d/dropbear/dropbear_2025.88-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/d/dropbear/dropbear_2025.89-1~deb13u1.dsc .github/workflows/build.yml | 8 +++ CHANGES | 57 ++++++++++++++++++++++ Makefile.in | 2 configure | 14 ++--- configure.ac | 7 -- debian/changelog | 18 +++++++ debian/salsa-ci.yml | 5 ++ debian/tests/control | 1 release.sh | 2 src/auth.h | 5 ++ src/cli-main.c | 12 ++++ src/common-channel.c | 17 ++++-- src/common-chansession.c | 2 src/config.h.in | 3 + src/default_options.h | 8 ++- src/loginrec.c | 25 ++-------- src/loginrec.h | 6 +- src/runopts.h | 1 src/scp.c | 38 +++++++++++---- src/session.h | 6 ++ src/svr-agentfwd.c | 14 ++++- src/svr-auth.c | 110 ++++++++++++++++++++++++++++++++++++++++++-- src/svr-authpubkey.c | 10 +++- src/svr-authpubkeyoptions.c | 6 ++ src/svr-chansession.c | 34 ++++--------- src/svr-kex.c | 8 +++ src/svr-runopts.c | 24 +++++++++ src/svr-tcpfwd.c | 5 ++ src/sysoptions.h | 23 ++++++--- test/test_dropbear.py | 2 30 files changed, 376 insertions(+), 97 deletions(-) diff -Nru dropbear-2025.88/.github/workflows/build.yml dropbear-2025.89/.github/workflows/build.yml --- dropbear-2025.88/.github/workflows/build.yml 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/.github/workflows/build.yml 2025-12-16 14:30:00.000000000 +0000 @@ -78,6 +78,9 @@ # 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 @@ -90,6 +93,9 @@ # 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 @@ -227,6 +233,8 @@ echo "#define DROPBEAR_SVR_PASSWORD_AUTH 0" >> localoptions.h # 1 second timeout is too short sed -i "s/DEFAULT_IDLE_TIMEOUT 1/DEFAULT_IDLE_TIMEOUT 99/" localoptions.h + # DROPBEAR_SVR_DROP_PRIVS is on by default, turn it off + echo "#define DROPBEAR_SVR_DROP_PRIVS 0" >> localoptions.h - name: make run: | diff -Nru dropbear-2025.88/CHANGES dropbear-2025.89/CHANGES --- dropbear-2025.88/CHANGES 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/CHANGES 2025-12-16 14:30:00.000000000 +0000 @@ -1,3 +1,60 @@ +2025.89 - 16 December 2025 + +- Security: Avoid privilege escalation via unix stream forwarding in Dropbear + server. Other programs on a system may authenticate unix sockets via + SO_PEERCRED, which would be root user for Dropbear forwarded connections, + allowing root privilege escalation. + Reported by Turistu, and thanks for advice on the fix. + This is tracked as CVE-2025-14282, and affects 2024.84 to 2025.88. + + It is fixed by dropping privileges of the dropbear process after + authentication. Unix stream sockets are now disallowed when a + forced command is used, either with authorized_key restrictions or + "dropbear -c command". + + In previous affected releases running with "dropbear -j" (will also disable + TCP fowarding) or building with localoptions.h/distrooptions.h + "#define DROPBEAR_SVR_LOCALSTREAMFWD 0" is a mitigation. + +- Security: Include scp fix for CVE-2019-6111. This allowed + a malicious server to overwrite arbitrary local files. + The missing fix was reported by Ashish Kunwar. + +- Server dropping privileges post-auth is enabled by default. This requires + setresgid() support, so some platforms such as netbsd or macos will have to + disable DROPBEAR_SVR_DROP_PRIVS in localoptions.h. Unix stream forwarding is + not available if DROPBEAR_SVR_DROP_PRIVS is disabled. + + Remote server TCP socket forwarding will now use OS privileged port + restrictions rather than having a fixed "allow >=1024 for non-root" rule. + + A future release may implement privilege dropping for netbsd/macos. + +- Fix a regression in 2025.87 when RSA and DSS are not built. This would lead + to a crash at startup with bad_bufptr(). + Reported by Dani Schmitt and Sebastian Priebe. + +- Don't limit channel window to 500MB. That is could cause stuck connections + if peers advise a large window and don't send an increment within 500MB. + Affects SSH.NET https://github.com/sshnet/SSH.NET/issues/1671 + Reported by Rob Hague. + +- Ignore -g -s when passwords arent enabled. Patch from Norbert Lange. + Ignore -m (disable MOTD), -j/-k (tcp forwarding) when not enabled. + +- Report SIGBUS and SIGTRAP signals. Patch from Loïc Mangeonjean. + +- Fix incorrect server auth delay. Was meant to be 250-350ms, it was actually + 150-350ms or possibly negative (zero). Reported by pickaxprograms. + +- Fix building without public key options. Thanks to Konstantin Demin + +- Fix building with proxycmd but without netcat. Thanks to Konstantin Demin + +- Fix incorrect path documentation for distrooptions, thanks to Todd Zullinger + +- Fix SO_REUSEADDR for TCP tests, reported by vt-alt. + 2025.88 - 7 May 2025 - Security: Don't allow dbclient hostname arguments to be interpreted diff -Nru dropbear-2025.88/Makefile.in dropbear-2025.89/Makefile.in --- dropbear-2025.88/Makefile.in 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/Makefile.in 2025-12-16 14:30:00.000000000 +0000 @@ -290,7 +290,7 @@ cd $(srcdir); ./dropbear_lint.sh check: lint - make -C test + $(MAKE) -C test ## Fuzzing targets diff -Nru dropbear-2025.88/configure dropbear-2025.89/configure --- dropbear-2025.88/configure 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/configure 2025-12-16 14:30:00.000000000 +0000 @@ -2979,13 +2979,6 @@ -# Record which revision is being built -if test -s "`which hg`" && test -d "$srcdir/.hg"; then - hgrev=`hg id -i -R "$srcdir"` - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Source directory Mercurial base revision $hgrev" >&5 -printf "%s\n" "$as_me: Source directory Mercurial base revision $hgrev" >&6;} -fi - ORIGCFLAGS="$CFLAGS" LATE_CFLAGS="" # Checks for programs. @@ -7597,6 +7590,13 @@ 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 + # Might be a macro. Might be sys/endian.h on BSDs ac_fn_c_check_header_compile "$LINENO" "endian.h" "ac_cv_header_endian_h" "$ac_includes_default" diff -Nru dropbear-2025.88/configure.ac dropbear-2025.89/configure.ac --- dropbear-2025.88/configure.ac 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/configure.ac 2025-12-16 14:30:00.000000000 +0000 @@ -8,12 +8,6 @@ AC_PREREQ([2.59]) AC_INIT -# Record which revision is being built -if test -s "`which hg`" && test -d "$srcdir/.hg"; then - hgrev=`hg id -i -R "$srcdir"` - AC_MSG_NOTICE([Source directory Mercurial base revision $hgrev]) -fi - ORIGCFLAGS="$CFLAGS" LATE_CFLAGS="" # Checks for programs. @@ -545,6 +539,7 @@ AC_CHECK_FUNCS(endutxent getutxent getutxid getutxline pututxline ) AC_CHECK_FUNCS(setutxent utmpxname) AC_CHECK_FUNCS(logout updwtmp logwtmp) +AC_CHECK_FUNCS(setresgid) # Might be a macro. Might be sys/endian.h on BSDs AC_CHECK_HEADERS([endian.h]) diff -Nru dropbear-2025.88/debian/changelog dropbear-2025.89/debian/changelog --- dropbear-2025.88/debian/changelog 2025-07-08 21:05:41.000000000 +0000 +++ dropbear-2025.89/debian/changelog 2025-12-16 19:36:49.000000000 +0000 @@ -1,3 +1,21 @@ +dropbear (2025.89-1~deb13u1) trixie-security; urgency=high + + * New upstream security and bugfix release (closes: #1123069). + + Fix CVE-2025-14282: Privilege escalation via unix stream forwarding in + Dropbear server. Other programs on a system may authenticate unix + sockets via SO_PEERCRED, which would be root user for Dropbear forwarded + connections, allowing root privilege escalation. + + The server now drops privileges of the dropbear process after + authentication. + + Remote server TCP socket forwarding will now use OS privileged port + restrictions rather than having a fixed "allow >=1024 for non-root" + rule. + + Unix stream sockets are now disallowed when a forced command is used, + either with authorized_key restrictions or "dropbear -c command". + * DEP-8: Add "Depends: e2fsprogs" to remote-unlocking test. + + -- Guilhem Moulin Tue, 16 Dec 2025 20:36:49 +0100 + dropbear (2025.88-2) unstable; urgency=medium [ Guilhem Moulin ] diff -Nru dropbear-2025.88/debian/salsa-ci.yml dropbear-2025.89/debian/salsa-ci.yml --- dropbear-2025.88/debian/salsa-ci.yml 2025-07-08 21:05:41.000000000 +0000 +++ dropbear-2025.89/debian/salsa-ci.yml 2025-12-16 19:36:49.000000000 +0000 @@ -1,3 +1,8 @@ --- include: - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml + +variables: + RELEASE: 'trixie' + SALSA_CI_DISABLE_REPROTEST: 1 + SALSA_CI_DISABLE_LINTIAN: 1 diff -Nru dropbear-2025.88/debian/tests/control dropbear-2025.89/debian/tests/control --- dropbear-2025.88/debian/tests/control 2025-07-08 21:05:41.000000000 +0000 +++ dropbear-2025.89/debian/tests/control 2025-12-16 19:36:49.000000000 +0000 @@ -10,6 +10,7 @@ # Only dependencies required to set the VM here are listed here; # dropbear-initramfs is not listed since we only install it in the VM. Depends: cryptsetup-initramfs, + e2fsprogs, fdisk, initramfs-tools-core, linux-image-generic, diff -Nru dropbear-2025.88/release.sh dropbear-2025.89/release.sh --- dropbear-2025.88/release.sh 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/release.sh 2025-12-16 14:30:00.000000000 +0000 @@ -9,7 +9,7 @@ TESTREL=0 fi -VERSION=$(echo '#include "src/default_options.h"\n#include "src/sysoptions.h"\necho DROPBEAR_VERSION' | cpp -DHAVE_CRYPT - | sh) +VERSION=$(echo '#include "src/default_options.h"\n#include "src/sysoptions.h"\necho DROPBEAR_VERSION' | cpp -DHAVE_CRYPT -DHAVE_SETRESGID - | sh) if [ $TESTREL -eq 1 ]; then echo Making test tarball for "$VERSION" ... diff -Nru dropbear-2025.88/src/auth.h dropbear-2025.89/src/auth.h --- dropbear-2025.88/src/auth.h 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/auth.h 2025-12-16 14:30:00.000000000 +0000 @@ -40,12 +40,16 @@ 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); int svr_pubkey_allows_tcpfwd(void); int svr_pubkey_allows_x11fwd(void); int svr_pubkey_allows_pty(void); +int svr_pubkey_has_forced_command(void); int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port); void svr_pubkey_set_forced_command(struct ChanSess *chansess); void svr_pubkey_options_cleanup(void); @@ -56,6 +60,7 @@ #define svr_pubkey_allows_tcpfwd() 1 #define svr_pubkey_allows_x11fwd() 1 #define svr_pubkey_allows_pty() 1 +#define svr_pubkey_has_forced_command() 0 static inline int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { (void)host; (void)port; return 1; } diff -Nru dropbear-2025.88/src/cli-main.c dropbear-2025.89/src/cli-main.c --- dropbear-2025.88/src/cli-main.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/cli-main.c 2025-12-16 14:30:00.000000000 +0000 @@ -77,7 +77,11 @@ } #if DROPBEAR_CLI_PROXYCMD - if (cli_opts.proxycmd || cli_opts.proxyexec) { + if (cli_opts.proxycmd +#if DROPBEAR_CLI_MULTIHOP + || cli_opts.proxyexec +#endif + ) { cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid); if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR || signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR || @@ -110,11 +114,13 @@ dropbear_exit("Failed to run '%s'\n", cmd); } +#if DROPBEAR_CLI_MULTIHOP static void exec_proxy_cmd(const void *unused) { (void)unused; run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd); dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]); } +#endif static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) { char * cmd_arg = NULL; @@ -145,9 +151,11 @@ cmd_arg = m_malloc(shell_cmdlen); snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd); exec_fn = shell_proxy_cmd; +#if DROPBEAR_CLI_MULTIHOP } else { /* No shell */ exec_fn = exec_proxy_cmd; +#endif } ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out); @@ -159,6 +167,7 @@ cleanup: m_free(cli_opts.proxycmd); m_free(cmd_arg); +#if DROPBEAR_CLI_MULTIHOP if (cli_opts.proxyexec) { char **a = NULL; for (a = cli_opts.proxyexec; *a; a++) { @@ -166,6 +175,7 @@ } m_free(cli_opts.proxyexec); } +#endif } static void kill_proxy_sighandler(int UNUSED(signo)) { diff -Nru dropbear-2025.88/src/common-channel.c dropbear-2025.89/src/common-channel.c --- dropbear-2025.88/src/common-channel.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/common-channel.c 2025-12-16 14:30:00.000000000 +0000 @@ -858,17 +858,21 @@ void recv_msg_channel_window_adjust() { struct Channel * channel; - unsigned int incr; + unsigned int incr, newwin; channel = getchannel(); incr = buf_getint(ses.payload); - TRACE(("received window increment %d", incr)) - incr = MIN(incr, TRANS_MAX_WIN_INCR); + TRACE(("received window increment %u", incr)) - channel->transwindow += incr; - channel->transwindow = MIN(channel->transwindow, TRANS_MAX_WINDOW); - + newwin = channel->transwindow + incr; + if (newwin < channel->transwindow) { + /* Integer overflow, clamp it at maximum. + * Behaviour may be unexpected, senders MUST NOT overflow per rfc4254. */ + TRACE(("overflow window, prev %u", channel->transwindow)); + newwin = 0xffffffff; + } + channel->transwindow = newwin; } /* Increment the incoming data window for a channel, and let the remote @@ -906,7 +910,6 @@ remotechan = buf_getint(ses.payload); transwindow = buf_getint(ses.payload); - transwindow = MIN(transwindow, TRANS_MAX_WINDOW); transmaxpacket = buf_getint(ses.payload); transmaxpacket = MIN(transmaxpacket, TRANS_MAX_PAYLOAD_LEN); diff -Nru dropbear-2025.88/src/common-chansession.c dropbear-2025.89/src/common-chansession.c --- dropbear-2025.88/src/common-chansession.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/common-chansession.c 2025-12-16 14:30:00.000000000 +0000 @@ -28,6 +28,7 @@ const struct SigMap signames[] = { {SIGABRT, "ABRT"}, {SIGALRM, "ALRM"}, + {SIGBUS, "BUS"}, {SIGFPE, "FPE"}, {SIGHUP, "HUP"}, {SIGILL, "ILL"}, @@ -37,6 +38,7 @@ {SIGQUIT, "QUIT"}, {SIGSEGV, "SEGV"}, {SIGTERM, "TERM"}, + {SIGTRAP, "TRAP"}, {SIGUSR1, "USR1"}, {SIGUSR2, "USR2"}, {0, NULL} diff -Nru dropbear-2025.88/src/config.h.in dropbear-2025.89/src/config.h.in --- dropbear-2025.88/src/config.h.in 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/config.h.in 2025-12-16 14:30:00.000000000 +0000 @@ -231,6 +231,9 @@ /* Define to 1 if you have the 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 diff -Nru dropbear-2025.88/src/default_options.h dropbear-2025.89/src/default_options.h --- dropbear-2025.88/src/default_options.h 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/default_options.h 2025-12-16 14:30:00.000000000 +0000 @@ -9,7 +9,7 @@ used if it exists in the build directory. Options defined there will override any options in this file. -Customisations will also be taken from src/distoptions.h if it exists. +Customisations will also be taken from src/distrooptions.h if it exists. Options can also be defined with -DDROPBEAR_XXX=[0,1] in Makefile CFLAGS @@ -303,6 +303,12 @@ /* -T server option overrides */ #define MAX_AUTH_TRIES 10 +/* Change server process to user privileges after authentication. */ +#ifndef DROPBEAR_SVR_DROP_PRIVS +/* Default is enabled. Should only be disabled if platforms are incompatible */ +#define DROPBEAR_SVR_DROP_PRIVS DROPBEAR_SVR_MULTIUSER +#endif + /* Delay introduced before closing an unauthenticated session (seconds). Disabled by default, can be set to say 30 seconds to reduce the speed of password brute forcing. Note that there is a risk of denial of diff -Nru dropbear-2025.88/src/loginrec.c dropbear-2025.89/src/loginrec.c --- dropbear-2025.88/src/loginrec.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/loginrec.c 2025-12-16 14:30:00.000000000 +0000 @@ -193,32 +193,24 @@ * * Call with a pointer to a struct logininfo initialised with * login_init_entry() or login_alloc_entry() - * - * Returns: - * >0 if successful - * 0 on failure (will use OpenSSH's logging facilities for diagnostics) */ -int +void login_login (struct logininfo *li) { li->type = LTYPE_LOGIN; - return login_write(li); + login_write(li); } /* login_logout(struct logininfo *) - Record a logout * * Call as with login_login() - * - * Returns: - * >0 if successful - * 0 on failure (will use OpenSSH's logging facilities for diagnostics) */ -int +void login_logout(struct logininfo *li) { li->type = LTYPE_LOGOUT; - return login_write(li); + login_write(li); } @@ -309,15 +301,9 @@ ** login_write: Call low-level recording functions based on autoconf ** results **/ -int +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 @@ -340,7 +326,6 @@ #ifdef USE_WTMPX wtmpx_write_entry(li); #endif - return 0; } #ifdef LOGIN_NEEDS_UTMPX diff -Nru dropbear-2025.88/src/loginrec.h dropbear-2025.89/src/loginrec.h --- dropbear-2025.88/src/loginrec.h 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/loginrec.h 2025-12-16 14:30:00.000000000 +0000 @@ -161,8 +161,8 @@ void login_set_current_time(struct logininfo *li); /* record the entry */ -int login_login (struct logininfo *li); -int login_logout(struct logininfo *li); +void login_login (struct logininfo *li); +void login_logout(struct logininfo *li); #ifdef LOGIN_NEEDS_UTMPX int login_utmp_only(struct logininfo *li); #endif @@ -170,7 +170,7 @@ /** End of public functions */ /* record the entry */ -int login_write (struct logininfo *li); +void login_write (struct logininfo *li); int login_log_entry(struct logininfo *li); /* produce various forms of the line filename */ diff -Nru dropbear-2025.88/src/runopts.h dropbear-2025.89/src/runopts.h --- dropbear-2025.88/src/runopts.h 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/runopts.h 2025-12-16 14:30:00.000000000 +0000 @@ -61,6 +61,7 @@ int readhostkey(const char * filename, sign_key * hostkey, enum signkey_type *type); void load_all_hostkeys(void); +void disable_sig_except(enum signature_type sig_type); typedef struct svr_runopts { diff -Nru dropbear-2025.88/src/scp.c dropbear-2025.89/src/scp.c --- dropbear-2025.88/src/scp.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/scp.c 2025-12-16 14:30:00.000000000 +0000 @@ -76,6 +76,8 @@ #include "includes.h" /*RCSID("$OpenBSD: scp.c,v 1.130 2006/01/31 10:35:43 djm Exp $");*/ +#include + #include "atomicio.h" #include "compat.h" #include "scpmisc.h" @@ -291,14 +293,14 @@ uid_t userid; int errs, remin, remout; -int pflag, iamremote, iamrecursive, targetshouldbedirectory; +int Tflag, pflag, iamremote, iamrecursive, targetshouldbedirectory; #define CMDNEEDS 64 char cmd[CMDNEEDS]; /* must hold "rcp -r -p -d\0" */ int response(void); void rsource(char *, struct stat *); -void sink(int, char *[]); +void sink(int, char *[], const char *); void source(int, char *[]); void tolocal(int, char *[]); void toremote(char *, int, char *[]); @@ -325,8 +327,8 @@ args.list = NULL; addargs(&args, "%s", ssh_program); - fflag = tflag = 0; - while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q1246S:o:F:")) != -1) + fflag = Tflag = tflag = 0; + while ((ch = getopt(argc, argv, "dfl:prtTvBCc:i:P:q1246S:o:F:")) != -1) switch (ch) { /* User-visible flags. */ case '1': @@ -389,9 +391,12 @@ setmode(0, O_BINARY); #endif break; + case 'T': + Tflag = 1; + break; default: usage(); - } + } argc -= optind; argv += optind; @@ -409,7 +414,7 @@ } if (tflag) { /* Receive data. */ - sink(argc, argv); + sink(argc, argv, NULL); exit(errs != 0); } if (argc < 2) @@ -589,7 +594,7 @@ continue; } xfree(bp); - sink(1, argv + argc - 1); + sink(1, argv + argc - 1, src); (void) close(remin); remin = remout = -1; } @@ -822,7 +827,7 @@ } void -sink(int argc, char **argv) +sink(int argc, char **argv, const char *src) { static BUF buffer; struct stat stb; @@ -836,6 +841,7 @@ off_t size, statbytes; int setimes, targisdir, wrerrno = 0; char ch, *cp, *np, *targ, *why, *vect[1], buf[2048]; + char *src_copy = NULL, *restrict_pattern = NULL; struct timeval tv[2]; #define atime tv[0] @@ -857,6 +863,17 @@ (void) atomicio(vwrite, remout, "", 1); if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) targisdir = 1; + if (src != NULL && !iamrecursive && !Tflag) { + /* + * Prepare to try to restrict incoming filenames to match + * the requested destination file glob. + */ + if ((src_copy = strdup(src)) == NULL) + fatal("strdup failed"); + if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) { + *restrict_pattern++ = '\0'; + } + } for (first = 1;; first = 0) { cp = buf; if (atomicio(read, remin, cp, 1) != 1) @@ -939,6 +956,9 @@ run_err("error: unexpected filename: %s", cp); exit(1); } + if (restrict_pattern != NULL && + fnmatch(restrict_pattern, cp, 0) != 0) + SCREWUP("filename does not match request"); if (targisdir) { static char *namebuf = NULL; static size_t cursize = 0; @@ -977,7 +997,7 @@ goto bad; } vect[0] = xstrdup(np); - sink(1, vect); + sink(1, vect, src); if (setimes) { setimes = 0; if (utimes(vect[0], tv) < 0) diff -Nru dropbear-2025.88/src/session.h dropbear-2025.89/src/session.h --- dropbear-2025.88/src/session.h 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/session.h 2025-12-16 14:30:00.000000000 +0000 @@ -276,6 +276,12 @@ /* 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 { diff -Nru dropbear-2025.88/src/svr-agentfwd.c dropbear-2025.89/src/svr-agentfwd.c --- dropbear-2025.88/src/svr-agentfwd.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-agentfwd.c 2025-12-16 14:30:00.000000000 +0000 @@ -151,7 +151,7 @@ if (chansess->agentfile != NULL && chansess->agentdir != NULL) { -#if DROPBEAR_SVR_MULTIUSER +#if !DROPBEAR_SVR_DROP_PRIVS /* Remove the dir as the user. That way they can't cause problems except * for themselves */ uid = getuid(); @@ -160,6 +160,9 @@ (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("Failed to set euid"); } +#else + (void)uid; + (void)gid; #endif /* 2 for "/" and "\0" */ @@ -172,7 +175,7 @@ rmdir(chansess->agentdir); -#if DROPBEAR_SVR_MULTIUSER +#if !DROPBEAR_SVR_DROP_PRIVS if ((seteuid(uid)) < 0 || (setegid(gid)) < 0) { dropbear_exit("Failed to revert euid"); @@ -219,7 +222,7 @@ gid_t gid; int ret = DROPBEAR_FAILURE; -#if DROPBEAR_SVR_MULTIUSER +#if !DROPBEAR_SVR_DROP_PRIVS /* drop to user privs to make the dir/file */ uid = getuid(); gid = getgid(); @@ -227,6 +230,9 @@ (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("Failed to set euid"); } +#else + (void)uid; + (void)gid; #endif memset((void*)&addr, 0x0, sizeof(addr)); @@ -267,7 +273,7 @@ out: -#if DROPBEAR_SVR_MULTIUSER +#if !DROPBEAR_SVR_DROP_PRIVS if ((seteuid(uid)) < 0 || (setegid(gid)) < 0) { dropbear_exit("Failed to revert euid"); diff -Nru dropbear-2025.88/src/svr-auth.c dropbear-2025.89/src/svr-auth.c --- dropbear-2025.88/src/svr-auth.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-auth.c 2025-12-16 14:30:00.000000000 +0000 @@ -389,9 +389,9 @@ /* Desired total delay 300ms +-50ms (in nanoseconds). Beware of integer overflow if increasing these values */ - const int mindelay = 250000000; - const unsigned int vardelay = 100000000; - suseconds_t rand_delay; + const uint32_t mindelay = 250000000; + const uint32_t vardelay = 100000000; + uint32_t rand_delay; struct timespec delay; gettime_wrapper(&delay); @@ -407,7 +407,7 @@ genrandom((unsigned char*)&rand_delay, sizeof(rand_delay)); rand_delay = mindelay + (rand_delay % vardelay); - if (delay.tv_sec == 0 && delay.tv_nsec <= mindelay) { + if (delay.tv_sec == 0 && delay.tv_nsec <= rand_delay) { /* Compensate for elapsed time */ delay.tv_nsec = rand_delay - delay.tv_nsec; } else { @@ -457,12 +457,23 @@ /* authdone must be set after encrypt_packet() for * delayed-zlib mode */ ses.authstate.authdone = 1; + +#if DROPBEAR_SVR_DROP_PRIVS + /* Drop privileges as soon as authentication has happened. */ + svr_switch_user(); +#endif ses.connect_time = 0; +#if DROPBEAR_SVR_DROP_PRIVS + /* If running as the user, we can rely on the OS + * to limit allowed ports */ + ses.allowprivport = 1; +#else if (ses.authstate.pw_uid == 0) { ses.allowprivport = 1; } +#endif /* Remove from the list of pre-auth sockets. Should be m_close(), since if * we fail, we might end up leaking connection slots, and disallow new @@ -472,3 +483,94 @@ TRACE(("leave 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. + * + * This may be called either after authentication, or + * after shell/command fork if DROPBEAR_SVR_DROP_PRIVS is unset. + */ +void svr_switch_user(void) { + assert(ses.authstate.authdone); + + /* We can only change uid/gid as root ... */ + if (getuid() == 0) { + + if ((setgid(ses.authstate.pw_gid) < 0) || + (initgroups(ses.authstate.pw_name, + 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"); + } + } else { + /* ... but if the daemon is the same uid as the requested uid, we don't + * need to */ + + /* XXX - there is a minor issue here, in that if there are multiple + * usernames with the same uid, but differing groups, then the + * differing groups won't be set (as with initgroups()). The solution + * is for the sysadmin not to give out the UID twice */ + if (getuid() != ses.authstate.pw_uid) { + dropbear_exit("Couldn't change user as non-root"); + } + } +} + +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 +} diff -Nru dropbear-2025.88/src/svr-authpubkey.c dropbear-2025.89/src/svr-authpubkey.c --- dropbear-2025.88/src/svr-authpubkey.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-authpubkey.c 2025-12-16 14:30:00.000000000 +0000 @@ -186,12 +186,14 @@ #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 key->sk_flags_mask = SSH_SK_USER_PRESENCE_REQD; +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->no_touch_required_flag) { key->sk_flags_mask &= ~SSH_SK_USER_PRESENCE_REQD; } if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->verify_required_flag) { key->sk_flags_mask |= SSH_SK_USER_VERIFICATION_REQD; } +#endif /* DROPBEAR_SVR_PUBKEY_OPTIONS */ #endif /* create the data which has been signed - this a string containing @@ -513,7 +515,13 @@ line_num++; ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen, - keyblob, keybloblen, &ses.authstate.pubkey_info); + keyblob, keybloblen, +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT + &ses.authstate.pubkey_info +#else + NULL +#endif + ); if (ret == DROPBEAR_SUCCESS) { break; } diff -Nru dropbear-2025.88/src/svr-authpubkeyoptions.c dropbear-2025.89/src/svr-authpubkeyoptions.c --- dropbear-2025.88/src/svr-authpubkeyoptions.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-authpubkeyoptions.c 2025-12-16 14:30:00.000000000 +0000 @@ -70,6 +70,12 @@ return 1; } +/* Returns 1 if a forced command pubkey option is set */ +int svr_pubkey_has_forced_command(void) { + return ses.authstate.pubkey_options + && ses.authstate.pubkey_options->forced_command; +} + /* Returns 1 if pubkey allows x11 forwarding, * 0 otherwise */ int svr_pubkey_allows_x11fwd() { diff -Nru dropbear-2025.88/src/svr-chansession.c dropbear-2025.89/src/svr-chansession.c --- dropbear-2025.88/src/svr-chansession.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-chansession.c 2025-12-16 14:30:00.000000000 +0000 @@ -326,7 +326,11 @@ 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 @@ * 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 @@ -980,30 +988,8 @@ #endif /* DEBUG_VALGRIND */ } -#if DROPBEAR_SVR_MULTIUSER - /* We can only change uid/gid as root ... */ - if (getuid() == 0) { - - if ((setgid(ses.authstate.pw_gid) < 0) || - (initgroups(ses.authstate.pw_name, - ses.authstate.pw_gid) < 0)) { - dropbear_exit("Error changing user group"); - } - if (setuid(ses.authstate.pw_uid) < 0) { - dropbear_exit("Error changing user"); - } - } else { - /* ... but if the daemon is the same uid as the requested uid, we don't - * need to */ - - /* XXX - there is a minor issue here, in that if there are multiple - * usernames with the same uid, but differing groups, then the - * differing groups won't be set (as with initgroups()). The solution - * is for the sysadmin not to give out the UID twice */ - if (getuid() != ses.authstate.pw_uid) { - dropbear_exit("Couldn't change user as non-root"); - } - } +#if !DROPBEAR_SVR_DROP_PRIVS + svr_switch_user(); #endif /* set env vars */ diff -Nru dropbear-2025.88/src/svr-kex.c dropbear-2025.89/src/svr-kex.c --- dropbear-2025.88/src/svr-kex.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-kex.c 2025-12-16 14:30:00.000000000 +0000 @@ -99,6 +99,14 @@ } #endif + if (!ses.kexstate.donesecondkex) { + /* Disable other signature types. + * During future rekeying, privileges may have been dropped + * so other keys won't be loadable. + * This must occur after send_msg_ext_info() which uses the hostkey list */ + disable_sig_except(ses.newkeys->algo_signature); + } + ses.requirenext = SSH_MSG_NEWKEYS; TRACE(("leave recv_msg_kexdh_init")) } diff -Nru dropbear-2025.88/src/svr-runopts.c dropbear-2025.89/src/svr-runopts.c --- dropbear-2025.88/src/svr-runopts.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-runopts.c 2025-12-16 14:30:00.000000000 +0000 @@ -253,6 +253,9 @@ case 'j': svr_opts.nolocaltcp = 1; break; +#else + case 'j': + break; #endif #if DROPBEAR_SVR_REMOTETCPFWD case 'k': @@ -261,6 +264,9 @@ case 'a': opts.listen_fwd_all = 1; break; +#else + case 'k': + break; #endif #if INETD_MODE case 'i': @@ -289,6 +295,9 @@ case 'm': svr_opts.domotd = 0; break; +#else + case 'm': + break; #endif case 'w': svr_opts.norootlogin = 1; @@ -323,6 +332,10 @@ case 't': svr_opts.multiauthmethod = 1; break; +#else + case 's': + case 'g': + break; #endif case 'h': printhelp(argv[0]); @@ -514,6 +527,17 @@ } } } + +void disable_sig_except(enum signature_type allow_type) { + int i; + TRACE(("Disabling other sigs except %d", allow_type)); + for (i = 0; sigalgs[i].name != NULL; i++) { + enum signature_type sig_type = sigalgs[i].val; + if (sig_type != allow_type) { + sigalgs[i].usable = 0; + } + } +} static void loadhostkey_helper(const char *name, void** src, void** dst, int fatal_duplicate) { if (*dst) { diff -Nru dropbear-2025.88/src/svr-tcpfwd.c dropbear-2025.89/src/svr-tcpfwd.c --- dropbear-2025.88/src/svr-tcpfwd.c 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/svr-tcpfwd.c 2025-12-16 14:30:00.000000000 +0000 @@ -346,6 +346,11 @@ TRACE(("streamlocal channel %d", channel->index)) + if (svr_opts.forced_command || svr_pubkey_has_forced_command()) { + TRACE(("leave newstreamlocal: no unix forwarding for forced command")) + goto out; + } + if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) { TRACE(("leave newstreamlocal: local unix forwarding disabled")) goto out; diff -Nru dropbear-2025.88/src/sysoptions.h dropbear-2025.89/src/sysoptions.h --- dropbear-2025.88/src/sysoptions.h 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/src/sysoptions.h 2025-12-16 14:30:00.000000000 +0000 @@ -4,7 +4,7 @@ *******************************************************************/ #ifndef DROPBEAR_VERSION -#define DROPBEAR_VERSION "2025.88" +#define DROPBEAR_VERSION "2025.89" #endif /* IDENT_VERSION_PART is the optional part after "SSH-2.0-dropbear". Refer to RFC4253 for requirements. */ @@ -207,7 +207,7 @@ /* LTC SHA384 depends on SHA512 */ #define DROPBEAR_SHA512 ((DROPBEAR_SHA2_512_HMAC) || (DROPBEAR_ECC_521) \ || (DROPBEAR_SHA384) || (DROPBEAR_DH_GROUP16) \ - || (DROPBEAR_ED25519)) + || (DROPBEAR_ED25519) || (DROPBEAR_SNTRUP761)) #define DROPBEAR_DH_GROUP14 ((DROPBEAR_DH_GROUP14_SHA256) || (DROPBEAR_DH_GROUP14_SHA1)) @@ -243,9 +243,6 @@ #define RECV_MAX_PACKET_LEN (MAX(35000, ((RECV_MAX_PAYLOAD_LEN)+100))) /* for channel code */ -#define TRANS_MAX_WINDOW 500000000 /* 500MB is sufficient, stopping overflow */ -#define TRANS_MAX_WIN_INCR 500000000 /* overflow prevention */ - #define RECV_WINDOWEXTEND (opts.recv_window / 3) /* We send a "window extend" every RECV_WINDOWEXTEND bytes */ #define MAX_RECV_WINDOW (10*1024*1024) /* 10 MB should be enough */ @@ -269,7 +266,7 @@ #else /* 521 bit ecdsa key */ #define MAX_PUBKEY_SIZE 200 -#define MAX_PRIVKEY_SIZE 200 +#define MAX_PRIVKEY_SIZE 250 #endif /* For kex hash buffer, worst case size for Q_C || Q_S || K */ @@ -335,7 +332,7 @@ /* PAM requires ./configure --enable-pam */ #if !defined(HAVE_LIBPAM) && DROPBEAR_SVR_PAM_AUTH -#error "DROPBEAR_SVR_PATM_AUTH requires PAM headers. Perhaps ./configure --enable-pam ?" +#error "DROPBEAR_SVR_PAM_AUTH requires PAM headers. Perhaps ./configure --enable-pam ?" #endif #if DROPBEAR_SVR_PASSWORD_AUTH && !HAVE_CRYPT @@ -358,6 +355,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 @@ -443,6 +444,14 @@ #define DROPBEAR_MULTI 0 #endif +#if !DROPBEAR_SVR_MULTIUSER && DROPBEAR_SVR_DROP_PRIVS +#error DROPBEAR_SVR_DROP_PRIVS needs DROPBEAR_SVR_MULTIUSER +#endif + +#if !(DROPBEAR_SVR_DROP_PRIVS || !DROPBEAR_SVR_MULTIUSER) && DROPBEAR_SVR_LOCALSTREAMFWD +#error DROPBEAR_SVR_LOCALSTREAMFWD requires DROPBEAR_SVR_DROP_PRIVS or !DROPBEAR_SVR_MULTIUSER +#endif + /* Fuzzing expects all key types to be enabled */ #if DROPBEAR_FUZZ #if defined(DROPBEAR_DSS) diff -Nru dropbear-2025.88/test/test_dropbear.py dropbear-2025.89/test/test_dropbear.py --- dropbear-2025.88/test/test_dropbear.py 2025-05-07 14:30:00.000000000 +0000 +++ dropbear-2025.89/test/test_dropbear.py 2025-12-16 14:30:00.000000000 +0000 @@ -76,7 +76,7 @@ class HandleTcp(socketserver.ThreadingMixIn, socketserver.TCPServer): # override TCPServer's default, avoids TIME_WAIT - allow_reuse_addr = True + allow_reuse_address = True """ Listens for a single incoming request, sends a response if given, and returns the inbound data.