Skip to content

Commit 281e4bc

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
Treat combined Match User/Group blocks as a conjunction in wolfsshd
1 parent c8a347d commit 281e4bc

6 files changed

Lines changed: 460 additions & 102 deletions

File tree

apps/wolfsshd/auth.c

Lines changed: 158 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,10 +1084,10 @@ static int DoCheckUser(const char* usr, WOLFSSHD_AUTH* auth)
10841084
* scoped to this branch.
10851085
*
10861086
* A NULL return means the root user's configuration could not be
1087-
* resolved (e.g. getgrgid() failure inside wolfSSHD_AuthGetUserConf).
1088-
* Fail closed and reject the login in that case rather than falling
1089-
* back to the global node, since denying root on an unresolvable
1090-
* configuration is the safe choice. */
1087+
* resolved (e.g. group set enumeration failed inside
1088+
* wolfSSHD_AuthGetUserConf). Fail closed and reject the login in that
1089+
* case rather than falling back to the global node, since denying root
1090+
* on an unresolvable configuration is the safe choice. */
10911091
usrConf = wolfSSHD_AuthGetUserConf(auth, usr, NULL, NULL, NULL, NULL,
10921092
NULL);
10931093
if (usrConf == NULL || wolfSSHD_ConfigGetPermitRoot(usrConf) == 0) {
@@ -1194,7 +1194,7 @@ static int RequestAuthentication(WS_UserAuthData* authData,
11941194
* non-Match users while enforcing Match restrictions.
11951195
*
11961196
* A NULL return here means the user's configuration could not be resolved
1197-
* (e.g. the primary group is unresolvable and getgrgid() failed inside
1197+
* (e.g. the user's group set could not be enumerated inside
11981198
* wolfSSHD_AuthGetUserConf). DoCheckUser has already confirmed the user
11991199
* exists, so this is a rare edge. Fail closed rather than fall back to the
12001200
* permissive global node: such a user cannot complete a session anyway
@@ -1752,6 +1752,144 @@ int wolfSSHD_AuthReducePermissions(WOLFSSHD_AUTH* auth)
17521752
#else
17531753
#define WGETGROUPLIST(x,y,z,w) getgrouplist((x),(y),(z),(w))
17541754
#endif
1755+
1756+
/* Initial guess and upper bound for the number of groups a user can be in.
1757+
* getgrouplist cannot be reliably sized with a NULL probe (macOS returns
1758+
* success with size 0 and, when the buffer is too small, echoes the input size
1759+
* rather than the needed count), so the buffer grows from the guess up to the
1760+
* bound. */
1761+
#ifndef WOLFSSHD_GROUP_LIST_INIT
1762+
#define WOLFSSHD_GROUP_LIST_INIT 32
1763+
#endif
1764+
#ifndef WOLFSSHD_GROUP_LIST_MAX
1765+
#define WOLFSSHD_GROUP_LIST_MAX 65536
1766+
#endif
1767+
1768+
/* frees a group name array previously built by wolfSSHD_GetUserGroupNames */
1769+
WOLFSSHD_STATIC void wolfSSHD_FreeUserGroupNames(void* heap, char** names,
1770+
word32 count)
1771+
{
1772+
word32 i;
1773+
1774+
if (names != NULL) {
1775+
for (i = 0; i < count; i++) {
1776+
WFREE(names[i], heap, DYNTYPE_SSHD);
1777+
}
1778+
WFREE(names, heap, DYNTYPE_SSHD);
1779+
}
1780+
}
1781+
1782+
/* Builds the list of group names the user belongs to, primary plus
1783+
* supplementary, so Match Group can be evaluated against the full set. On
1784+
* success sets *outNames to an owned array of owned names and *outCount to the
1785+
* entry count; the caller frees them with wolfSSHD_FreeUserGroupNames. Returns
1786+
* WS_SUCCESS on success, leaving *outNames NULL on failure. */
1787+
WOLFSSHD_STATIC int wolfSSHD_GetUserGroupNames(void* heap, const char* usr,
1788+
WGID_T primaryGid, char*** outNames, word32* outCount)
1789+
{
1790+
int ret = WS_SUCCESS;
1791+
int grpListSz = 0;
1792+
int allocSz;
1793+
int res;
1794+
int i;
1795+
gid_t* grpList = NULL;
1796+
char** names = NULL;
1797+
struct group* g;
1798+
word32 count = 0;
1799+
1800+
*outNames = NULL;
1801+
*outCount = 0;
1802+
1803+
#if defined(__QNX__) || defined(__QNXNTO__)
1804+
/* QNX cannot report the size ahead of time, so allocate the max and fill
1805+
* once; getgrouplist returns 0 on success there. */
1806+
allocSz = (int)sysconf(_SC_NGROUPS_MAX);
1807+
if (allocSz <= 0) {
1808+
ret = WS_FATAL_ERROR;
1809+
}
1810+
if (ret == WS_SUCCESS) {
1811+
grpList = (gid_t*)WMALLOC(sizeof(gid_t) * allocSz, heap, DYNTYPE_SSHD);
1812+
if (grpList == NULL) {
1813+
ret = WS_MEMORY_E;
1814+
}
1815+
}
1816+
if (ret == WS_SUCCESS) {
1817+
grpListSz = allocSz;
1818+
res = WGETGROUPLIST(usr, primaryGid, grpList, &grpListSz);
1819+
if (res != 0) {
1820+
ret = WS_FATAL_ERROR;
1821+
}
1822+
}
1823+
#else
1824+
/* Grow the buffer until the lookup fits: a NULL probe is unreliable and a
1825+
* too-small buffer does not report the needed count on all platforms. On
1826+
* success grpListSz holds the actual number of groups. */
1827+
allocSz = WOLFSSHD_GROUP_LIST_INIT;
1828+
res = -1;
1829+
while (ret == WS_SUCCESS && res < 0) {
1830+
grpList = (gid_t*)WMALLOC(sizeof(gid_t) * allocSz, heap, DYNTYPE_SSHD);
1831+
if (grpList == NULL) {
1832+
ret = WS_MEMORY_E;
1833+
break;
1834+
}
1835+
1836+
grpListSz = allocSz;
1837+
res = WGETGROUPLIST(usr, primaryGid, grpList, &grpListSz);
1838+
if (res < 0) {
1839+
/* buffer too small: discard, grow, and retry up to the cap */
1840+
WFREE(grpList, heap, DYNTYPE_SSHD);
1841+
grpList = NULL;
1842+
if (allocSz >= WOLFSSHD_GROUP_LIST_MAX) {
1843+
ret = WS_FATAL_ERROR;
1844+
break;
1845+
}
1846+
allocSz *= 2;
1847+
if (allocSz > WOLFSSHD_GROUP_LIST_MAX) {
1848+
allocSz = WOLFSSHD_GROUP_LIST_MAX;
1849+
}
1850+
}
1851+
}
1852+
#endif
1853+
1854+
if (ret == WS_SUCCESS) {
1855+
names = (char**)WMALLOC(sizeof(char*) * grpListSz, heap, DYNTYPE_SSHD);
1856+
if (names == NULL) {
1857+
ret = WS_MEMORY_E;
1858+
}
1859+
}
1860+
1861+
if (ret == WS_SUCCESS) {
1862+
for (i = 0; i < grpListSz; i++) {
1863+
/* Skip gids that do not resolve to a name rather than failing the
1864+
* login, matching OpenSSH. Copy immediately, since getgrgid reuses
1865+
* a static buffer the next call overwrites. */
1866+
g = getgrgid(grpList[i]);
1867+
if (g == NULL || g->gr_name == NULL) {
1868+
continue;
1869+
}
1870+
names[count] = WSTRDUP(g->gr_name, heap, DYNTYPE_SSHD);
1871+
if (names[count] == NULL) {
1872+
ret = WS_MEMORY_E;
1873+
break;
1874+
}
1875+
count++;
1876+
}
1877+
}
1878+
1879+
if (ret == WS_SUCCESS) {
1880+
*outNames = names;
1881+
*outCount = count;
1882+
}
1883+
else {
1884+
wolfSSHD_FreeUserGroupNames(heap, names, count);
1885+
}
1886+
1887+
if (grpList != NULL) {
1888+
WFREE(grpList, heap, DYNTYPE_SSHD);
1889+
}
1890+
1891+
return ret;
1892+
}
17551893
#endif /* WIN32 */
17561894

17571895
/* sets the extended groups the user is in, returns WS_SUCCESS on success */
@@ -1827,32 +1965,38 @@ WOLFSSHD_CONFIG* wolfSSHD_AuthGetUserConf(const WOLFSSHD_AUTH* auth,
18271965
const char* adr)
18281966
{
18291967
WOLFSSHD_CONFIG* ret = NULL;
1968+
char** grpNames = NULL;
1969+
word32 grpCount = 0;
18301970

18311971
if (auth != NULL) {
1832-
char* gName = NULL;
1833-
18341972
if (usr != NULL) {
18351973
#ifdef WIN32
1836-
//LogonUserEx()
1974+
/* LogonUserEx(): group lookup is not implemented on Windows, so
1975+
* Match Group directives do not apply here */
18371976
#else
18381977
struct passwd* p_passwd;
1839-
struct group* g = NULL;
18401978

18411979
p_passwd = getpwnam((const char *)usr);
18421980
if (p_passwd == NULL) {
18431981
return NULL;
18441982
}
18451983

1846-
g = getgrgid(p_passwd->pw_gid);
1847-
if (g == NULL) {
1984+
/* Resolve the full group set (primary and supplementary) so a
1985+
* Match Group directive matches on any of the user's groups.
1986+
* Fail closed if the groups cannot be enumerated. */
1987+
if (wolfSSHD_GetUserGroupNames(auth->heap, usr, p_passwd->pw_gid,
1988+
&grpNames, &grpCount) != WS_SUCCESS) {
18481989
return NULL;
18491990
}
1850-
gName = g->gr_name;
18511991
#endif
18521992
}
18531993

1854-
ret = wolfSSHD_GetUserConf(auth->conf, usr, gName, host, localAdr,
1855-
localPort, RDomain, adr);
1994+
ret = wolfSSHD_GetUserConf(auth->conf, usr, (const char**)grpNames,
1995+
grpCount, host, localAdr, localPort, RDomain, adr);
1996+
1997+
#ifndef WIN32
1998+
wolfSSHD_FreeUserGroupNames(auth->heap, grpNames, grpCount);
1999+
#endif
18562000
}
18572001
return ret;
18582002
}

apps/wolfsshd/auth.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ int wolfSSHD_GetHomeDirectory(WOLFSSHD_AUTH* auth, WOLFSSH* ssh, WCHAR* out, int
8484
#ifndef _WIN32
8585
extern int (*wsshd_setregid_cb)(WGID_T, WGID_T);
8686
extern int (*wsshd_setreuid_cb)(WUID_T, WUID_T);
87+
int wolfSSHD_GetUserGroupNames(void* heap, const char* usr, WGID_T primaryGid,
88+
char*** outNames, word32* outCount);
89+
void wolfSSHD_FreeUserGroupNames(void* heap, char** names, word32 count);
8790
#endif
8891
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
8992
int CheckPasswordHashUnix(const char* input, char* stored);

apps/wolfsshd/configuration.c

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,32 +1397,58 @@ static int ConfigLoad(WOLFSSHD_CONFIG* conf, const char* filename, int depth)
13971397

13981398
/* returns the config associated with the user */
13991399
WOLFSSHD_CONFIG* wolfSSHD_GetUserConf(const WOLFSSHD_CONFIG* conf,
1400-
const char* usr, const char* grp, const char* host,
1400+
const char* usr, const char** grps, word32 grpCount, const char* host,
14011401
const char* localAdr, word16* localPort, const char* RDomain,
14021402
const char* adr)
14031403
{
14041404
WOLFSSHD_CONFIG* ret;
14051405
WOLFSSHD_CONFIG* current;
1406+
int matches;
1407+
word32 i;
14061408

14071409
/* default to return head of list */
14081410
ret = current = (WOLFSSHD_CONFIG*)conf;
14091411
while (current != NULL) {
1410-
/* compare current configs user */
1411-
if (usr != NULL && current->usrAppliesTo != NULL) {
1412-
if (XSTRCMP(current->usrAppliesTo, usr) == 0) {
1413-
ret = current;
1414-
break;
1412+
/* A node is a Match candidate only if it carries at least one
1413+
* selector. Every non-NULL selector on the node must match for the
1414+
* node to apply, so a combined 'Match User X Group Y' is treated as a
1415+
* conjunction the same way OpenSSH treats a Match line. A NULL
1416+
* selector acts as a wildcard. */
1417+
matches = 0;
1418+
if (current->usrAppliesTo != NULL || current->groupAppliesTo != NULL) {
1419+
matches = 1;
1420+
1421+
if (current->usrAppliesTo != NULL) {
1422+
if (usr == NULL ||
1423+
XSTRCMP(current->usrAppliesTo, usr) != 0) {
1424+
matches = 0;
1425+
}
14151426
}
1416-
}
14171427

1418-
/* compare current configs group */
1419-
if (grp != NULL && current->groupAppliesTo != NULL) {
1420-
if (XSTRCMP(current->groupAppliesTo, grp) == 0) {
1421-
ret = current;
1422-
break;
1428+
/* The group selector matches when it equals any group the user
1429+
* belongs to, primary or supplementary, mirroring how OpenSSH
1430+
* evaluates 'Match Group'. An empty group list matches no group
1431+
* selector, so combined blocks fail closed. */
1432+
if (matches && current->groupAppliesTo != NULL) {
1433+
matches = 0;
1434+
if (grps != NULL) {
1435+
for (i = 0; i < grpCount; i++) {
1436+
if (grps[i] == NULL)
1437+
continue;
1438+
if (XSTRCMP(current->groupAppliesTo, grps[i]) == 0) {
1439+
matches = 1;
1440+
break;
1441+
}
1442+
}
1443+
}
14231444
}
14241445
}
14251446

1447+
if (matches) {
1448+
ret = current;
1449+
break;
1450+
}
1451+
14261452
current = current->next;
14271453
}
14281454

apps/wolfsshd/configuration.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ long wolfSSHD_ConfigGetGraceTime(const WOLFSSHD_CONFIG* conf);
6363
byte wolfSSHD_ConfigGetPwAuth(const WOLFSSHD_CONFIG* conf);
6464
byte wolfSSHD_ConfigGetPubKeyAuth(const WOLFSSHD_CONFIG* conf);
6565
WOLFSSHD_CONFIG* wolfSSHD_GetUserConf(const WOLFSSHD_CONFIG* conf,
66-
const char* usr, const char* grp, const char* host,
66+
const char* usr, const char** grps, word32 grpCount, const char* host,
6767
const char* localAdr, word16* localPort, const char* RDomain,
6868
const char* adr);
6969
void wolfSSHD_ConfigSavePID(const WOLFSSHD_CONFIG* conf);

apps/wolfsshd/include.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ apps_wolfsshd_test_test_configuration_SOURCES = apps/wolfsshd/test/test_configur
1717
apps/wolfsshd/auth.h
1818
apps_wolfsshd_test_test_configuration_LDADD = src/libwolfssh.la
1919
apps_wolfsshd_test_test_configuration_DEPENDENCIES = src/libwolfssh.la
20-
apps_wolfsshd_test_test_configuration_CPPFLAGS = $(AM_CPPFLAGS) -DWOLFSSH_SSHD -DWOLFSSHD_UNIT_TEST -I$(srcdir)/apps/wolfsshd/
20+
# Force the smallest initial group buffer so test_GetUserGroupNames exercises
21+
# the getgrouplist buffer-growth/realloc loop under the sanitizers.
22+
apps_wolfsshd_test_test_configuration_CPPFLAGS = $(AM_CPPFLAGS) -DWOLFSSH_SSHD -DWOLFSSHD_UNIT_TEST -DWOLFSSHD_GROUP_LIST_INIT=1 -I$(srcdir)/apps/wolfsshd/
2123

2224
endif BUILD_SSHD

0 commit comments

Comments
 (0)