@@ -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}
0 commit comments