Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions apps/wolfsshd/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
#if defined(WOLFSSHD_UNIT_TEST) && !defined(_WIN32)
int (*wsshd_setregid_cb)(WGID_T, WGID_T) = setregid;
int (*wsshd_setreuid_cb)(WUID_T, WUID_T) = setreuid;
int (*wsshd_setegid_cb)(WGID_T) = setegid;
int (*wsshd_seteuid_cb)(WUID_T) = seteuid;
#endif

struct WOLFSSHD_AUTH {
Expand Down Expand Up @@ -1841,7 +1843,7 @@ static int SetDefaultPublicKeyCheck(WOLFSSHD_AUTH* auth)
#define WOLFSSH_USER_GET_STRING(x) #x
#define WOLFSSH_USER_STRING(x) WOLFSSH_USER_GET_STRING(x)

static int SetDefualtUserID(WOLFSSHD_AUTH* auth)
static int SetDefaultUserID(WOLFSSHD_AUTH* auth)
{
#ifdef _WIN32
/* TODO: Implement for Windows. */
Expand All @@ -1850,6 +1852,15 @@ static int SetDefualtUserID(WOLFSSHD_AUTH* auth)
struct passwd* pwInfo;
int ret = WS_SUCCESS;

if (wolfSSHD_ConfigGetPrivilegeSeparation(auth->conf) ==

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [Medium] Privilege-separation permission behavior lacks regression tests
💡 SUGGEST test

The PR changes daemon privilege behavior by returning success from SetDefaultUserID when UsePrivilegeSeparation no is configured, and by making wolfSSHD_AuthRaisePermissions a no-op unless the config is WOLFSSHD_PRIV_SEPARAT or WOLFSSHD_PRIV_SANDBOX. Grep found config parsing tests and reduce-permission tests, but no tests that exercise wolfSSHD_AuthRaisePermissions or auth creation under the new privilege modes. This is a user-visible daemon startup/auth behavior change, so it should have regression coverage.

Suggestion:

Suggested change
if (wolfSSHD_ConfigGetPrivilegeSeparation(auth->conf) ==
Add unit coverage for `UsePrivilegeSeparation no` returning success without requiring the configured sshd user, and for `wolfSSHD_AuthRaisePermissions` calling the uid/gid restore path only for `yes` and `sandbox` modes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit coverage for the UsePrivilegeSeparation no case to ensure it returns success without requiring the configured sshd user. I also added tests verifying that wolfSSHD_AuthRaisePermissions only triggers the uid/gid restore path for the yes and sandbox modes as expected.

WOLFSSHD_PRIV_OFF) {
auth->gid = getgid();
auth->uid = getuid();
auth->sGid = auth->gid;
auth->sUid = auth->uid;
return WS_SUCCESS;
}

pwInfo = getpwnam(WOLFSSH_USER_STRING(WOLFSSH_SSHD_USER));
if (pwInfo == NULL) {
/* user name not found on system */
Expand Down Expand Up @@ -1910,7 +1921,7 @@ WOLFSSHD_AUTH* wolfSSHD_AuthCreateUser(void* heap, const WOLFSSHD_CONFIG* conf)
}

if (ret == WS_SUCCESS) {
ret = SetDefualtUserID(auth);
ret = SetDefaultUserID(auth);
if (ret != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting default "
"user ID.");
Expand Down Expand Up @@ -1942,24 +1953,37 @@ int wolfSSHD_AuthFreeUser(WOLFSSHD_AUTH* auth)
/* return WS_SUCCESS on success */
int wolfSSHD_AuthRaisePermissions(WOLFSSHD_AUTH* auth)
{
int ret = 0;
int ret = WS_SUCCESS;

wolfSSH_Log(WS_LOG_INFO, "[SSHD] Attempting to raise permissions level");
#ifndef WIN32

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] New bare scope block violates local C convention
🔧 NIT convention

The PR adds a standalone scope block under #ifndef WIN32 only to limit flag. The project coding guidance treats newly added bare scope blocks as a convention issue; this can be written with the declaration inside the existing preprocessor region instead.

Suggestion:

Suggested change
#ifndef WIN32
#ifndef WIN32
byte flag = 0;
if (auth == NULL) {
return WS_BAD_ARGUMENT;
}
flag = wolfSSHD_ConfigGetPrivilegeSeparation(auth->conf);
if (flag == WOLFSSHD_PRIV_SEPARAT || flag == WOLFSSHD_PRIV_SANDBOX) {
...
}
#endif

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the styling.

if (auth) {
byte flag = 0;

if (auth == NULL) {
return WS_BAD_ARGUMENT;
}

flag = wolfSSHD_ConfigGetPrivilegeSeparation(auth->conf);
if (flag == WOLFSSHD_PRIV_SEPARAT || flag == WOLFSSHD_PRIV_SANDBOX) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Attempting to raise permissions level");
#ifdef WOLFSSHD_UNIT_TEST
if (wsshd_setegid_cb(auth->sGid) != 0) {
#else
if (setegid(auth->sGid) != 0) {
#endif
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error raising gid");
ret = WS_FATAL_ERROR;
}

if (seteuid(auth->sUid) != 0) {
#ifdef WOLFSSHD_UNIT_TEST
if (ret == WS_SUCCESS && wsshd_seteuid_cb(auth->sUid) != 0) {
#else
if (ret == WS_SUCCESS && seteuid(auth->sUid) != 0) {
#endif
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error raising uid");
ret = WS_FATAL_ERROR;
}
}
else {
ret = WS_BAD_ARGUMENT;
}
#endif

return ret;
Expand Down
2 changes: 2 additions & 0 deletions apps/wolfsshd/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ int wolfSSHD_OpenSecureFile(const char* path, WUID_T ownerUid,
#ifndef _WIN32
extern int (*wsshd_setregid_cb)(WGID_T, WGID_T);
extern int (*wsshd_setreuid_cb)(WUID_T, WUID_T);
extern int (*wsshd_setegid_cb)(WGID_T);
extern int (*wsshd_seteuid_cb)(WUID_T);
int wolfSSHD_GetUserGroupNames(void* heap, const char* usr, WGID_T primaryGid,
char*** outNames, word32* outCount);
void wolfSSHD_FreeUserGroupNames(void* heap, char** names, word32 count);
Expand Down
266 changes: 266 additions & 0 deletions apps/wolfsshd/test/test_configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,266 @@ static int test_AuthReducePermissionsUser_uid_fail(void)
wsshd_setreuid_cb = savedReuid;
return ret;
}

static WGID_T s_setegid_arg;
static WUID_T s_seteuid_arg;
static int s_setegid_ret;
static int s_seteuid_ret;
static int s_setegid_called;
static int s_seteuid_called;

static int stub_setegid(WGID_T egid)
{
s_setegid_called = 1;
s_setegid_arg = egid;
return s_setegid_ret;
}

static int stub_seteuid(WUID_T euid)
{
s_seteuid_called = 1;
s_seteuid_arg = euid;
return s_seteuid_ret;
}

static void InstallPrivRaiseStubs(int egidRet, int euidRet,
int (**savedEgid)(WGID_T), int (**savedEuid)(WUID_T))
{
*savedEgid = wsshd_setegid_cb;
*savedEuid = wsshd_seteuid_cb;
wsshd_setegid_cb = stub_setegid;
wsshd_seteuid_cb = stub_seteuid;
s_setegid_ret = egidRet;
s_seteuid_ret = euidRet;
s_setegid_called = 0;
s_seteuid_called = 0;
s_setegid_arg = 0;
s_seteuid_arg = 0;
}

/* UsePrivilegeSeparation no must let SetDefaultUserID succeed without a
* configured sshd system user, since no uid/gid switching will ever happen. */
static int test_AuthCreateUser_privSepOff(void)
{
int ret = WS_SUCCESS;
WOLFSSHD_CONFIG* conf;
WOLFSSHD_AUTH* auth;
static const char line[] = "UsePrivilegeSeparation no";

conf = wolfSSHD_ConfigNew(NULL);
if (conf == NULL) {
return WS_MEMORY_E;
}

if (ParseConfigLine(&conf, line, (int)WSTRLEN(line), 0) != WS_SUCCESS) {
ret = WS_FATAL_ERROR;
}

if (ret == WS_SUCCESS) {
auth = wolfSSHD_AuthCreateUser(NULL, conf);
if (auth == NULL) {
ret = WS_FATAL_ERROR;
}
else {
wolfSSHD_AuthFreeUser(auth);
}
}

wolfSSHD_ConfigFree(conf);
return ret;
}

/* wolfSSHD_AuthRaisePermissions must not touch setegid/seteuid at all when
* privilege separation is off, since the process never dropped privileges. */
static int test_AuthRaisePermissions_offSkipsSyscalls(void)
{
int ret = WS_SUCCESS;
WOLFSSHD_CONFIG* conf;
WOLFSSHD_AUTH* auth;
int (*savedEgid)(WGID_T);
int (*savedEuid)(WUID_T);
static const char line[] = "UsePrivilegeSeparation no";

conf = wolfSSHD_ConfigNew(NULL);
if (conf == NULL) {
return WS_MEMORY_E;
}

if (ParseConfigLine(&conf, line, (int)WSTRLEN(line), 0) != WS_SUCCESS) {
ret = WS_FATAL_ERROR;
}

if (ret == WS_SUCCESS) {
auth = wolfSSHD_AuthCreateUser(NULL, conf);
if (auth == NULL) {
ret = WS_FATAL_ERROR;
}
else {
InstallPrivRaiseStubs(0, 0, &savedEgid, &savedEuid);

if (wolfSSHD_AuthRaisePermissions(auth) != WS_SUCCESS)
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS
&& (s_setegid_called || s_seteuid_called))
ret = WS_FATAL_ERROR;

wsshd_setegid_cb = savedEgid;
wsshd_seteuid_cb = savedEuid;
wolfSSHD_AuthFreeUser(auth);
}
}

wolfSSHD_ConfigFree(conf);
return ret;
}

/* With privilege separation on, wolfSSHD_AuthRaisePermissions restores the
* uid/gid the process started with (captured at AuthCreateUser time). Skipped
* when the environment has no "sshd" system user, since SetDefaultUserID
* requires one to succeed for this mode. */
static int test_AuthRaisePermissions_separateCallsSyscalls(void)
{
int ret = WS_SUCCESS;
WOLFSSHD_CONFIG* conf;
WOLFSSHD_AUTH* auth;
int (*savedEgid)(WGID_T);
int (*savedEuid)(WUID_T);

if (getpwnam("sshd") == NULL) {
/* no sshd system user available in this environment to switch to */
Log(" No \"sshd\" system user available, skipping.\n");
return WS_SUCCESS;
}

conf = wolfSSHD_ConfigNew(NULL);
if (conf == NULL) {
return WS_MEMORY_E;
}

/* privilege separation defaults to on */
auth = wolfSSHD_AuthCreateUser(NULL, conf);
if (auth == NULL) {
ret = WS_FATAL_ERROR;
}
else {
InstallPrivRaiseStubs(0, 0, &savedEgid, &savedEuid);

if (wolfSSHD_AuthRaisePermissions(auth) != WS_SUCCESS)
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && (!s_setegid_called || !s_seteuid_called))
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && s_setegid_arg != getgid())
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && s_seteuid_arg != getuid())
ret = WS_FATAL_ERROR;

wsshd_setegid_cb = savedEgid;
wsshd_seteuid_cb = savedEuid;
wolfSSHD_AuthFreeUser(auth);
}

wolfSSHD_ConfigFree(conf);
return ret;
}

/* wolfSSHD_AuthRaisePermissions must reject a NULL auth argument instead of
* dereferencing it. */
static int test_AuthRaisePermissions_nullArg(void)
{
if (wolfSSHD_AuthRaisePermissions(NULL) != WS_BAD_ARGUMENT)
return WS_FATAL_ERROR;
return WS_SUCCESS;
}

/* When setegid fails, wolfSSHD_AuthRaisePermissions must report the failure
* and short-circuit seteuid rather than attempting it anyway. */
static int test_AuthRaisePermissions_gidFailSkipsUid(void)
{
int ret = WS_SUCCESS;
WOLFSSHD_CONFIG* conf;
WOLFSSHD_AUTH* auth;
int (*savedEgid)(WGID_T);
int (*savedEuid)(WUID_T);

if (getpwnam("sshd") == NULL) {
/* no sshd system user available in this environment to switch to */
Log(" No \"sshd\" system user available, skipping.\n");
return WS_SUCCESS;
}

conf = wolfSSHD_ConfigNew(NULL);
if (conf == NULL) {
return WS_MEMORY_E;
}

/* privilege separation defaults to on */
auth = wolfSSHD_AuthCreateUser(NULL, conf);
if (auth == NULL) {
ret = WS_FATAL_ERROR;
}
else {
InstallPrivRaiseStubs(-1, 0, &savedEgid, &savedEuid);

if (wolfSSHD_AuthRaisePermissions(auth) != WS_FATAL_ERROR)
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && !s_setegid_called)
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && s_seteuid_called)
ret = WS_FATAL_ERROR;

wsshd_setegid_cb = savedEgid;
wsshd_seteuid_cb = savedEuid;
wolfSSHD_AuthFreeUser(auth);
}

wolfSSHD_ConfigFree(conf);
return ret;
}

/* When setegid succeeds but seteuid fails, wolfSSHD_AuthRaisePermissions must
* still report the failure. */
static int test_AuthRaisePermissions_uidFail(void)
{
int ret = WS_SUCCESS;
WOLFSSHD_CONFIG* conf;
WOLFSSHD_AUTH* auth;
int (*savedEgid)(WGID_T);
int (*savedEuid)(WUID_T);

if (getpwnam("sshd") == NULL) {
/* no sshd system user available in this environment to switch to */
Log(" No \"sshd\" system user available, skipping.\n");
return WS_SUCCESS;
}

conf = wolfSSHD_ConfigNew(NULL);
if (conf == NULL) {
return WS_MEMORY_E;
}

/* privilege separation defaults to on */
auth = wolfSSHD_AuthCreateUser(NULL, conf);
if (auth == NULL) {
ret = WS_FATAL_ERROR;
}
else {
InstallPrivRaiseStubs(0, -1, &savedEgid, &savedEuid);

if (wolfSSHD_AuthRaisePermissions(auth) != WS_FATAL_ERROR)
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && !s_setegid_called)
ret = WS_FATAL_ERROR;
if (ret == WS_SUCCESS && !s_seteuid_called)
ret = WS_FATAL_ERROR;

wsshd_setegid_cb = savedEgid;
wsshd_seteuid_cb = savedEuid;
wolfSSHD_AuthFreeUser(auth);
}

wolfSSHD_ConfigFree(conf);
return ret;
}
#endif /* !_WIN32 */

/* Locks in the NULL-safe comparison used by RequestAuthentication to fail
Expand Down Expand Up @@ -1929,6 +2189,12 @@ const TEST_CASE testCases[] = {
TEST_DECL(test_AuthReducePermissionsUser_ok),
TEST_DECL(test_AuthReducePermissionsUser_gid_fail),
TEST_DECL(test_AuthReducePermissionsUser_uid_fail),
TEST_DECL(test_AuthCreateUser_privSepOff),
TEST_DECL(test_AuthRaisePermissions_offSkipsSyscalls),
TEST_DECL(test_AuthRaisePermissions_separateCallsSyscalls),
TEST_DECL(test_AuthRaisePermissions_nullArg),
TEST_DECL(test_AuthRaisePermissions_gidFailSkipsUid),
TEST_DECL(test_AuthRaisePermissions_uidFail),
#endif
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
TEST_DECL(test_CheckPasswordHashUnix),
Expand Down
Loading
Loading