Skip to content

Commit c3a30c6

Browse files
Treat combined Match User/Group blocks as a conjunction in wolfsshd
1 parent 1efd647 commit c3a30c6

2 files changed

Lines changed: 123 additions & 11 deletions

File tree

apps/wolfsshd/configuration.c

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,26 +1309,40 @@ WOLFSSHD_CONFIG* wolfSSHD_GetUserConf(const WOLFSSHD_CONFIG* conf,
13091309
{
13101310
WOLFSSHD_CONFIG* ret;
13111311
WOLFSSHD_CONFIG* current;
1312+
int matches;
13121313

13131314
/* default to return head of list */
13141315
ret = current = (WOLFSSHD_CONFIG*)conf;
13151316
while (current != NULL) {
1316-
/* compare current configs user */
1317-
if (usr != NULL && current->usrAppliesTo != NULL) {
1318-
if (XSTRCMP(current->usrAppliesTo, usr) == 0) {
1319-
ret = current;
1320-
break;
1317+
/* A node is a Match candidate only if it carries at least one
1318+
* selector. Every non-NULL selector on the node must match for the
1319+
* node to apply, so a combined 'Match User X Group Y' is treated as a
1320+
* conjunction the same way OpenSSH treats a Match line. A NULL
1321+
* selector acts as a wildcard. */
1322+
matches = 0;
1323+
if (current->usrAppliesTo != NULL || current->groupAppliesTo != NULL) {
1324+
matches = 1;
1325+
1326+
if (current->usrAppliesTo != NULL) {
1327+
if (usr == NULL ||
1328+
XSTRCMP(current->usrAppliesTo, usr) != 0) {
1329+
matches = 0;
1330+
}
13211331
}
1322-
}
13231332

1324-
/* compare current configs group */
1325-
if (grp != NULL && current->groupAppliesTo != NULL) {
1326-
if (XSTRCMP(current->groupAppliesTo, grp) == 0) {
1327-
ret = current;
1328-
break;
1333+
if (matches && current->groupAppliesTo != NULL) {
1334+
if (grp == NULL ||
1335+
XSTRCMP(current->groupAppliesTo, grp) != 0) {
1336+
matches = 0;
1337+
}
13291338
}
13301339
}
13311340

1341+
if (matches) {
1342+
ret = current;
1343+
break;
1344+
}
1345+
13321346
current = current->next;
13331347
}
13341348

apps/wolfsshd/test/test_configuration.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,104 @@ static int test_MatchUnsupportedSelector(void)
571571
}
572572
wolfSSHD_ConfigFree(head);
573573
}
574+
return ret;
575+
}
576+
577+
/* A combined 'Match User X Group Y' directive is a conjunction: it applies only
578+
* to a user who satisfies BOTH selectors, matching OpenSSH semantics. This
579+
* locks in that wolfSSHD_GetUserConf does not return such a block for a user
580+
* who satisfies only one selector (the policy-bypass case), while single
581+
* selector 'Match User' and 'Match Group' blocks keep applying on their one
582+
* selector alone. */
583+
static int test_GetUserConfMatchGroupAnd(void)
584+
{
585+
int ret = WS_SUCCESS;
586+
WOLFSSHD_CONFIG* head;
587+
WOLFSSHD_CONFIG* conf;
588+
WOLFSSHD_CONFIG* combined;
589+
WOLFSSHD_CONFIG* userOnly;
590+
WOLFSSHD_CONFIG* groupOnly;
591+
WOLFSSHD_CONFIG* match;
592+
593+
head = wolfSSHD_ConfigNew(NULL);
594+
if (head == NULL)
595+
ret = WS_MEMORY_E;
596+
conf = head;
574597

598+
#define PCL(s) ParseConfigLine(&conf, s, (int)WSTRLEN(s), 0)
599+
/* restrictive global default: SFTP only for everyone */
600+
if (ret == WS_SUCCESS) ret = PCL("ForceCommand internal-sftp");
601+
602+
/* combined block relaxes the restriction, but only for a user who is both
603+
* 'alice' AND in group 'admins' */
604+
if (ret == WS_SUCCESS) ret = PCL("Match User alice Group admins");
605+
if (ret == WS_SUCCESS) ret = PCL("ForceCommand /bin/sh");
606+
if (ret == WS_SUCCESS) combined = conf;
607+
608+
/* single selector blocks must keep matching on their one selector */
609+
if (ret == WS_SUCCESS) ret = PCL("Match User bob");
610+
if (ret == WS_SUCCESS) ret = PCL("ForceCommand /bin/bob");
611+
if (ret == WS_SUCCESS) userOnly = conf;
612+
613+
if (ret == WS_SUCCESS) ret = PCL("Match Group staff");
614+
if (ret == WS_SUCCESS) ret = PCL("ForceCommand /bin/staff");
615+
if (ret == WS_SUCCESS) groupOnly = conf;
616+
#undef PCL
617+
618+
/* alice in admins satisfies both selectors -> gets the combined block */
619+
if (ret == WS_SUCCESS) {
620+
match = wolfSSHD_GetUserConf(head, "alice", "admins", NULL, NULL,
621+
NULL, NULL, NULL);
622+
if (match != combined)
623+
ret = WS_FATAL_ERROR;
624+
}
625+
626+
/* alice in a different group satisfies only the user selector -> must NOT
627+
* get the combined block; falls back to the restrictive global head */
628+
if (ret == WS_SUCCESS) {
629+
match = wolfSSHD_GetUserConf(head, "alice", "users", NULL, NULL,
630+
NULL, NULL, NULL);
631+
if (match != head)
632+
ret = WS_FATAL_ERROR;
633+
}
634+
635+
/* carol in admins satisfies only the group selector -> must NOT get the
636+
* combined block; falls back to the restrictive global head */
637+
if (ret == WS_SUCCESS) {
638+
match = wolfSSHD_GetUserConf(head, "carol", "admins", NULL, NULL,
639+
NULL, NULL, NULL);
640+
if (match != head)
641+
ret = WS_FATAL_ERROR;
642+
}
643+
644+
/* alice with no resolved group must NOT get the combined block: a NULL
645+
* group cannot satisfy the group selector, so it fails closed to the
646+
* global head. This is the WIN32 / failed group-lookup path where
647+
* wolfSSHD_AuthGetUserConf passes grp == NULL. */
648+
if (ret == WS_SUCCESS) {
649+
match = wolfSSHD_GetUserConf(head, "alice", NULL, NULL, NULL,
650+
NULL, NULL, NULL);
651+
if (match != head)
652+
ret = WS_FATAL_ERROR;
653+
}
654+
655+
/* single selector 'Match User bob' still applies on the user alone */
656+
if (ret == WS_SUCCESS) {
657+
match = wolfSSHD_GetUserConf(head, "bob", "anygroup", NULL, NULL,
658+
NULL, NULL, NULL);
659+
if (match != userOnly)
660+
ret = WS_FATAL_ERROR;
661+
}
662+
663+
/* single selector 'Match Group staff' still applies on the group alone */
664+
if (ret == WS_SUCCESS) {
665+
match = wolfSSHD_GetUserConf(head, "anyuser", "staff", NULL, NULL,
666+
NULL, NULL, NULL);
667+
if (match != groupOnly)
668+
ret = WS_FATAL_ERROR;
669+
}
670+
671+
wolfSSHD_ConfigFree(head);
575672
return ret;
576673
}
577674

@@ -1011,6 +1108,7 @@ const TEST_CASE testCases[] = {
10111108
TEST_DECL(test_ConfigCopy),
10121109
TEST_DECL(test_GetUserConfMatchOverride),
10131110
TEST_DECL(test_MatchUnsupportedSelector),
1111+
TEST_DECL(test_GetUserConfMatchGroupAnd),
10141112
TEST_DECL(test_CAKeysFileDiffers),
10151113
TEST_DECL(test_IncludeRecursionBound),
10161114
TEST_DECL(test_ConfigFree),

0 commit comments

Comments
 (0)