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
4 changes: 4 additions & 0 deletions apps/wolfsshd/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,11 @@ static int CheckPasswordHashUnix(const char* input, char* stored)
}
#endif /* WOLFSSH_HAVE_LIBCRYPT || WOLFSSH_HAVE_LIBLOGIN */

#ifdef WOLFSSHD_UNIT_TEST
int CheckPasswordUnix(const char* usr, const byte* pw, word32 pwSz, WOLFSSHD_AUTH* authCtx)
#else
static int CheckPasswordUnix(const char* usr, const byte* pw, word32 pwSz, WOLFSSHD_AUTH* authCtx)
#endif
{
int ret = WS_SUCCESS;
char* pwStr = NULL;
Expand Down
1 change: 1 addition & 0 deletions apps/wolfsshd/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ void wolfSSHD_FreeUserGroupNames(void* heap, char** names, word32 count);
#endif
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
int CheckPasswordHashUnix(const char* input, char* stored);
int CheckPasswordUnix(const char* usr, const byte* pw, word32 pwSz, WOLFSSHD_AUTH* authCtx);
#endif
int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
word32 keySz);
Expand Down
125 changes: 125 additions & 0 deletions apps/wolfsshd/test/test_configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include <wolfssh/ssh.h>
#include <wolfssl/wolfcrypt/coding.h>
#include <wolfssl/wolfcrypt/memory.h>
#include <configuration.h>
#include <auth.h>

Expand Down Expand Up @@ -1307,6 +1308,129 @@ static int test_CheckPasswordHashUnix(void)
}
}

/* (a) empty input + empty stored hash -> SUCCESS (pins the empty branch). */
if (ret == WS_SUCCESS) {
char emptyStored[2];
emptyStored[0] = 0;
Log(" Testing scenario: empty password + empty stored hash authenticates.");
rc = CheckPasswordHashUnix("", emptyStored);
if (rc == WSSHD_AUTH_SUCCESS) {
Log(" PASSED.\n");
}
else {
Log(" FAILED.\n");
ret = WS_FATAL_ERROR;
}
}

/* (b) empty input + real $6$ hash -> FAILURE (kills the && -> || mutant). */
if (ret == WS_SUCCESS) {
Log(" Testing scenario: empty password + real hash is rejected.");
rc = CheckPasswordHashUnix("", stored);
if (rc == WSSHD_AUTH_FAILURE) {
Log(" PASSED.\n");
}
else {
Log(" FAILED.\n");
ret = WS_FATAL_ERROR;
}
}

/* (c) non-empty input + empty stored hash -> FAILURE. */
if (ret == WS_SUCCESS) {
char emptyStored[2];
emptyStored[0] = 0;
Log(" Testing scenario: non-empty password + empty stored hash is rejected.");
rc = CheckPasswordHashUnix(correct, emptyStored);
if (rc == WSSHD_AUTH_FAILURE) {
Log(" PASSED.\n");
}
else {
Log(" FAILED.\n");
ret = WS_FATAL_ERROR;
}
}

/* (d) locked account (stored[0]=='*') -> FAILURE (pins the '*' guard). */
if (ret == WS_SUCCESS) {
char lockedStored[3];
lockedStored[0] = '*';
lockedStored[1] = 0;
Log(" Testing scenario: locked account is rejected.");
rc = CheckPasswordHashUnix(correct, lockedStored);
if (rc == WSSHD_AUTH_FAILURE) {
Log(" PASSED.\n");
}
else {
Log(" FAILED.\n");
ret = WS_FATAL_ERROR;
}
}

return ret;
}

/* Verify CheckPasswordUnix zeroes the pwStr buffer before freeing it. The
* cleanup ForceZero(pwStr, pwSz+1) in auth.c is otherwise uncovered; removing
* it leaves the plaintext password in freed heap memory. getpwnam() fails for
* the bogus user, so pwStr (size pwSz+1) is the only wolfSSL-heap allocation
* on this path and storedHashCpy stays NULL. The capturing allocator copies
* the buffer's bytes at free time so they can be inspected. */
static void* pwCapPtr = NULL;
static byte pwCapBuf[7]; /* pwSz(6) + 1 */
static int pwCapDone = 0;

static void* pwCapMalloc(size_t size)
{
void* p = malloc(size);
if (p != NULL && size == sizeof(pwCapBuf) && pwCapPtr == NULL)
pwCapPtr = p;
return p;
}

static void pwCapFree(void* ptr)
{
if (ptr != NULL && ptr == pwCapPtr) {
WMEMCPY(pwCapBuf, ptr, sizeof(pwCapBuf));
pwCapDone = 1;
}
free(ptr);
}

static void* pwCapRealloc(void* ptr, size_t size)
{
return realloc(ptr, size);
}

static int test_CheckPasswordUnix(void)
{
int ret = WS_SUCCESS;
word32 i;

pwCapPtr = NULL;
pwCapDone = 0;
WMEMSET(pwCapBuf, 0xA5, sizeof(pwCapBuf));

Log(" Testing scenario: CheckPasswordUnix wipes pwStr before free.");
if (wolfSSL_SetAllocators(pwCapMalloc, pwCapFree, pwCapRealloc) != 0) {
Log(" FAILED (set allocators).\n");
return WS_FATAL_ERROR;
}
(void)CheckPasswordUnix("wolfsshd-no-such-user-xyz",
(const byte*)"secret", 6, NULL);
wolfSSL_SetAllocators(NULL, NULL, NULL);

Comment on lines +1414 to +1422
if (!pwCapDone) {
Log(" FAILED (pwStr allocation not captured).\n");
return WS_FATAL_ERROR;
}
for (i = 0; i < (word32)sizeof(pwCapBuf); i++) {
if (pwCapBuf[i] != 0) {
ret = WS_FATAL_ERROR;
break;
}
}
Log(ret == WS_SUCCESS ? " PASSED.\n" : " FAILED.\n");
return ret;
}
#endif /* WOLFSSH_HAVE_LIBCRYPT || WOLFSSH_HAVE_LIBLOGIN */
Expand Down Expand Up @@ -1932,6 +2056,7 @@ const TEST_CASE testCases[] = {
#endif
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
TEST_DECL(test_CheckPasswordHashUnix),
TEST_DECL(test_CheckPasswordUnix),
#endif
};

Expand Down
87 changes: 87 additions & 0 deletions tests/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -5332,6 +5332,89 @@ static int test_SshResourceFree_zeroesSecrets(void)
return result;
}

/* Verify HandshakeInfoFree wipes the HandshakeInfo before releasing it:
* - hs->keys / hs->peerKeys: negotiated session enc+MAC keys
* - hs->e / hs->x: KEX public value and ephemeral private
* - hs->privKey: ephemeral KEX key union
* Mutation testing flagged the ForceZero(hs, sizeof(HandshakeInfo)) in
* HandshakeInfoFree (src/internal.c) as uncovered; removing it would leave
* this material in heap memory after free. wolfSSH_TestFreeHandshake exposes
* the free path; the retain-on-free allocator diverts the freed block so its
* bytes can be inspected. Only data fields are marked - pointer fields stay
* NULL (from wolfSSH_new zero-init) and the useX/kexHashId flags stay 0 so no
* per-key free runs over the poisoned bytes. */
static int test_HandshakeInfoFree_zeroesSecrets(void)
{
WOLFSSH_CTX* ctx = NULL;
WOLFSSH* ssh = NULL;
HandshakeInfo* hs = NULL;
word32 i;
const byte* p;
int result = 0;
int retainInstalled = 0;
wolfSSL_Malloc_cb prevMf = NULL;
wolfSSL_Free_cb prevFf = NULL;
wolfSSL_Realloc_cb prevRf = NULL;

ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
if (ctx == NULL)
return -760;
ssh = wolfSSH_new(ctx);
if (ssh == NULL) {
result = -761;
goto out;
}
hs = ssh->handshake;
if (hs == NULL) {
result = -762;
goto out;
}

WMEMSET(&hs->keys, 0xA5, sizeof(hs->keys));
WMEMSET(&hs->peerKeys, 0xA5, sizeof(hs->peerKeys));
WMEMSET(hs->e, 0xA5, sizeof(hs->e));
WMEMSET(hs->x, 0xA5, sizeof(hs->x));
WMEMSET(&hs->privKey, 0xA5, sizeof(hs->privKey));

wolfSSL_GetAllocators(&prevMf, &prevFf, &prevRf);
if (wolfSSL_SetAllocators(RetainMalloc, RetainFree,
RetainRealloc) != 0) {
result = -763;
goto out;
}
retainInstalled = 1;
wolfSSH_TestFreeHandshake(ssh); /* frees hs via HandshakeInfoFree */
wolfSSL_SetAllocators(prevMf, prevFf, prevRf);
retainInstalled = 0;

if (!IsRetained(hs)) {
result = -764;
goto out;
}

p = (const byte*)&hs->keys;
for (i = 0; i < (word32)sizeof(hs->keys); i++)
if (p[i] != 0) { result = -765; goto out; }
p = (const byte*)&hs->peerKeys;
for (i = 0; i < (word32)sizeof(hs->peerKeys); i++)
if (p[i] != 0) { result = -766; goto out; }
for (i = 0; i < (word32)sizeof(hs->e); i++)
if (hs->e[i] != 0) { result = -767; goto out; }
for (i = 0; i < (word32)sizeof(hs->x); i++)
if (hs->x[i] != 0) { result = -768; goto out; }
p = (const byte*)&hs->privKey;
for (i = 0; i < (word32)sizeof(hs->privKey); i++)
if (p[i] != 0) { result = -769; goto out; }

out:
if (retainInstalled)
wolfSSL_SetAllocators(prevMf, prevFf, prevRf);
DrainRetained();
if (ctx != NULL)
wolfSSH_CTX_free(ctx);
return result;
Comment on lines +5409 to +5415
}

#endif /* WOLFSSH_TEST_CAPTURING_ALLOCATOR */

#ifndef WOLFSSH_NO_DH
Expand Down Expand Up @@ -7845,6 +7928,10 @@ int wolfSSH_UnitTest(int argc, char** argv)
printf("SshResourceFree_zeroesSecrets: %s\n",
(unitResult == 0 ? "SUCCESS" : "FAILED"));
testResult = testResult || unitResult;
unitResult = test_HandshakeInfoFree_zeroesSecrets();
printf("HandshakeInfoFree_zeroesSecrets: %s\n",
(unitResult == 0 ? "SUCCESS" : "FAILED"));
testResult = testResult || unitResult;
#endif

#ifndef WOLFSSH_NO_DH
Expand Down
Loading