echoserver: drop root before serving with -F

A root-run echoserver with -F now switches to an unprivileged user once
the listener is bound and the keys are loaded, before any peer is
served, so forwarded connects and binds no longer run as root. The new
-u option names the user; the default is nobody, with a warning. A
failed switch ends the server.

- Add DropPrivileges(): setgroups, setgid, setuid, then check root
  cannot be regained
- Build it only for the standalone POSIX echoserver with WOLFSSH_FWD,
  not for the tests that run echoserver_test() in-process
- Warn on stderr that -u is ignored in a build without it
- Document -u in the usage text and README.md

Issue: F-11935
pull/1338/merge
John Safranek 2026-10-21 10:16:28 -07:00 committed by philljj
parent e7b7e8ccfe
commit bfafd5c9ab
2 changed files with 82 additions and 4 deletions

View File

@ -326,9 +326,12 @@ port forwarding support in wolfSSH.
The echoserver will handle local and remote port forwarding when started
with `-F`; without it every forwarding request is refused. Local forwards
may connect only to loopback destinations unless `-L` names the allowed
`host` or `host:port` entries, comma separated. To connect with
the ssh tool, using one of the following command lines. You can run either of
the ssh command lines from anywhere:
`host` or `host:port` entries, comma separated. Started as root with
`-F`, the echoserver runs as `nobody` once it is listening; `-u` names
another user. Shells run as that user too, so a login whose home
directory has mode 0700 gets no shell. To connect with the ssh tool, use
one of the following command lines. You can run either of the ssh command
lines from anywhere:
src/wolfssl$ ./examples/server/server
src/wolfssh$ ./examples/echoserver/echoserver -F

View File

@ -103,6 +103,18 @@
#include <sys/un.h>
#endif /* WOLFSSH_AGENT */
#if defined(WOLFSSH_FWD) && !defined(USE_WINDOWS_API) && \
!defined(WOLFSSL_NUCLEUS) && !defined(WOLFSSH_ZEPHYR) && \
!defined(NO_MAIN_DRIVER)
/* Gives up root before serving; forwarding opens sockets for the
* peer with the server's credentials. Standalone only: the tests run
* echoserver_test() inside their own process. */
#define ECHOSERVER_DROP_PRIVS
#include <grp.h>
#include <pwd.h>
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
@ -3834,6 +3846,36 @@ static int SetDefaultSftpPath(WOLFSSH* ssh, const char* defaultSftpPath,
#endif
#ifdef ECHOSERVER_DROP_PRIVS
/* Runs the process as userName for good. Without root this only checks
* that userName is the current user. Returns 0, or -1. */
static int DropPrivileges(const char* userName)
{
struct passwd* pw;
gid_t gid;
uid_t uid;
pw = getpwnam(userName);
if (pw == NULL)
return -1;
gid = pw->pw_gid;
uid = pw->pw_uid;
if (geteuid() != 0)
return (uid == geteuid()) ? 0 : -1;
if (setgroups(1, &gid) != 0 || setgid(gid) != 0 || setuid(uid) != 0)
return -1;
/* Make sure root cannot be taken back. */
if (uid != 0 && (setuid(0) == 0 || seteuid(0) == 0))
return -1;
return 0;
}
#endif
static void ShowUsage(void)
{
printf("echoserver %s linked with wolfSSL %s\n", LIBWOLFSSH_VERSION_STRING,
@ -3854,6 +3896,10 @@ static void ShowUsage(void)
" destinations the peer may connect to, in place\n"
" of the default of loopback only; repeatable\n");
#endif
#ifdef ECHOSERVER_DROP_PRIVS
printf(" -u <user> run as user once listening; started as root with\n"
" -F, the default is nobody\n");
#endif
#ifdef WOLFSSH_SFTP
printf(" -d <string> set the home directory for SFTP connections\n");
printf(" -D confine SFTP connections to the home directory,"
@ -3905,7 +3951,7 @@ static void ShowUsage(void)
}
#define ECHOSERVER_OPTLIST "?1a:Ad:DefEFL:p:R:Ni:j:i:I:J:K:P:k:b:x:m:c:s:G:HW:"
#define ECHOSERVER_OPTLIST "?1a:Ad:DefEFL:p:R:Ni:j:i:I:J:K:P:k:b:x:m:c:s:G:HW:u:"
#ifdef WOLFSSH_WINDOWS_CERT_STORE
/* Detects whether argv or the environment requests a host key from the
@ -4039,6 +4085,9 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
int appChannels = 0;
#ifdef WOLFSSH_FWD
int allowFwd = 0;
#endif
#ifdef ECHOSERVER_DROP_PRIVS
const char* dropUser = NULL;
#endif
int ch;
word16 port = wolfSshPort;
@ -4114,6 +4163,15 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
#endif
break;
case 'u':
#ifdef ECHOSERVER_DROP_PRIVS
dropUser = myoptarg;
#else
fprintf(stderr, "Warning: -u is ignored in this "
"build\n");
#endif
break;
case 'L':
#ifdef WOLFSSH_FWD
if (fwdAllowArgsSz >= FWD_ALLOW_ARGS_MAX) {
@ -4796,6 +4854,23 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
#endif
}
#ifdef ECHOSERVER_DROP_PRIVS
/* After the listener and the keys, before any peer is served. */
if (dropUser == NULL && allowFwd && geteuid() == 0) {
dropUser = "nobody";
fprintf(stderr, "Warning: running as nobody for -F; -u picks the "
"user.\n");
#ifdef WOLFSSH_SHELL
fprintf(stderr, "Warning: shells run as nobody too; a login whose "
"home has mode 0700 gets no shell.\n");
#endif
}
if (dropUser != NULL && DropPrivileges(dropUser) != 0) {
WCLOSESOCKET(listenFd);
ES_ERROR("Couldn't run as user %s.\n", dropUser);
}
#endif
SignalTcpReady(serverArgs->signal, port);
do {