Skip to content

Commit 7393aae

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
wolfsshd: reject Match blocks using unimplemented selectors
1 parent 633f627 commit 7393aae

2 files changed

Lines changed: 149 additions & 0 deletions

File tree

apps/wolfsshd/configuration.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,61 @@ static int AddRestrictedCase(WOLFSSHD_CONFIG* config, const char* mtch,
913913
}
914914

915915

916+
/* returns WS_SUCCESS when every selector keyword in 'value' is one that is
917+
* implemented. Only the User and Group selectors are handled. The Match value
918+
* is a whitespace separated list of "keyword argument" pairs; any keyword that
919+
* is not User or Group (Address, Host, LocalAddress, LocalPort, RDomain, ...),
920+
* and a Match with no selector at all (bare "Match", "Match all"), is rejected.
921+
* This also catches mixed forms such as "User alice Address 10.0.0.0/8" where
922+
* the unsupported selector would otherwise be silently dropped. */
923+
static int CheckMatchSelectors(const char* value)
924+
{
925+
int ret = WS_SUCCESS;
926+
int i = 0;
927+
int sz;
928+
int len;
929+
int found = 0;
930+
931+
sz = (value != NULL) ? (int)XSTRLEN(value) : 0;
932+
933+
while (ret == WS_SUCCESS && i < sz) {
934+
/* skip whitespace before the keyword */
935+
i += CountWhitespace(value + i, sz - i, 0);
936+
if (i >= sz) {
937+
break;
938+
}
939+
940+
/* length of the keyword token */
941+
len = CountWhitespace(value + i, sz - i, 1);
942+
if (len == (int)(sizeof("User") - 1) &&
943+
WSTRNCMP(value + i, "User", sizeof("User") - 1) == 0) {
944+
found = 1;
945+
}
946+
else if (len == (int)(sizeof("Group") - 1) &&
947+
WSTRNCMP(value + i, "Group", sizeof("Group") - 1) == 0) {
948+
found = 1;
949+
}
950+
else {
951+
ret = WS_FATAL_ERROR;
952+
}
953+
i += len;
954+
955+
if (ret == WS_SUCCESS) {
956+
/* skip whitespace then the argument token for this keyword */
957+
i += CountWhitespace(value + i, sz - i, 0);
958+
i += CountWhitespace(value + i, sz - i, 1);
959+
}
960+
}
961+
962+
/* a Match with no implemented selector at all is also rejected */
963+
if (ret == WS_SUCCESS && !found) {
964+
ret = WS_FATAL_ERROR;
965+
}
966+
967+
return ret;
968+
}
969+
970+
916971
/* returns WS_SUCCESS on success, on success it update the conf pointed to
917972
* and makes it point to the newly created conf node */
918973
static int HandleMatch(WOLFSSHD_CONFIG** conf, const char* value, int valueSz)
@@ -924,6 +979,19 @@ static int HandleMatch(WOLFSSHD_CONFIG** conf, const char* value, int valueSz)
924979
ret = WS_BAD_ARGUMENT;
925980
}
926981

982+
/* Only the User and Group selectors are implemented. Reject any Match
983+
* directive that names an unsupported selector (even when mixed with a
984+
* supported one) or names no selector at all, rather than accepting it and
985+
* silently dropping the unsupported part, which would fail open. */
986+
if (ret == WS_SUCCESS) {
987+
ret = CheckMatchSelectors(value);
988+
if (ret != WS_SUCCESS) {
989+
wolfSSH_Log(WS_LOG_ERROR,
990+
"[SSHD] Unsupported Match selector, only User and Group are "
991+
"handled");
992+
}
993+
}
994+
927995
/* create new configure for altered options specific to the match */
928996
if (ret == WS_SUCCESS) {
929997
newConf = wolfSSHD_ConfigCopy(*conf);
@@ -946,6 +1014,12 @@ static int HandleMatch(WOLFSSHD_CONFIG** conf, const char* value, int valueSz)
9461014

9471015
/* @TODO handle , separated user/group list */
9481016

1017+
/* on failure free the config that will not be added to the list */
1018+
if (ret != WS_SUCCESS && newConf != NULL) {
1019+
wolfSSHD_ConfigFree(newConf);
1020+
newConf = NULL;
1021+
}
1022+
9491023
/* update current config being processed */
9501024
if (ret == WS_SUCCESS) {
9511025
(*conf)->next = newConf;

apps/wolfsshd/test/test_configuration.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,80 @@ static int test_GetUserConfMatchOverride(void)
501501
return ret;
502502
}
503503

504+
/* Only the User and Group Match selectors are implemented. A Match block keyed
505+
* on any other selector (Address, Host, LocalAddress, LocalPort, RDomain) would
506+
* otherwise be accepted but never apply, a fail-open misconfiguration. Verify
507+
* that User/Group are accepted while the unsupported selectors are rejected. */
508+
static int test_MatchUnsupportedSelector(void)
509+
{
510+
int ret = WS_SUCCESS;
511+
int i;
512+
WOLFSSHD_CONFIG* head;
513+
WOLFSSHD_CONFIG* conf;
514+
515+
static CONFIG_LINE_VECTOR vectors[] = {
516+
/* supported selectors */
517+
{"Match User", "Match User testuser", 0},
518+
{"Match Group", "Match Group testgroup", 0},
519+
520+
/* combined supported selectors must be accepted, in either order */
521+
{"Match User+Group", "Match User alice Group dev", 0},
522+
{"Match Group+User", "Match Group dev User alice", 0},
523+
524+
/* unsupported selectors must be rejected, not silently ignored */
525+
{"Match Address", "Match Address 10.0.0.0/8", 1},
526+
{"Match Host", "Match Host example.com", 1},
527+
{"Match LocalAddress", "Match LocalAddress 192.168.1.1", 1},
528+
{"Match LocalPort", "Match LocalPort 22", 1},
529+
{"Match RDomain", "Match RDomain vrf-external", 1},
530+
531+
/* no-selector forms must also be rejected, not silently accepted */
532+
{"Match all", "Match all", 1},
533+
{"Bare Match", "Match", 1},
534+
535+
/* supported selector with no argument: passes the selector check but
536+
* fails while parsing the (missing) name, exercising the cleanup of
537+
* the already allocated config node */
538+
{"Match User no arg", "Match User", 1},
539+
540+
/* mixed supported+unsupported selectors must be rejected; the
541+
* unsupported part must not be silently dropped */
542+
{"Mixed User+Address", "Match User alice Address 10.0.0.0/8", 1},
543+
{"Mixed Group+Host", "Match Group dev Host example.com", 1},
544+
};
545+
const int numVectors = (int)(sizeof(vectors) / sizeof(*vectors));
546+
547+
head = wolfSSHD_ConfigNew(NULL);
548+
if (head == NULL) {
549+
ret = WS_MEMORY_E;
550+
}
551+
conf = head;
552+
553+
if (ret == WS_SUCCESS) {
554+
for (i = 0; i < numVectors; ++i) {
555+
int rc;
556+
557+
Log(" Testing scenario: %s.", vectors[i].desc);
558+
559+
rc = ParseConfigLine(&conf, vectors[i].line,
560+
(int)WSTRLEN(vectors[i].line), 0);
561+
562+
if ((rc == WS_SUCCESS && !vectors[i].shouldFail) ||
563+
(rc != WS_SUCCESS && vectors[i].shouldFail)) {
564+
Log(" PASSED.\n");
565+
}
566+
else {
567+
Log(" FAILED.\n");
568+
ret = WS_FATAL_ERROR;
569+
break;
570+
}
571+
}
572+
wolfSSHD_ConfigFree(head);
573+
}
574+
575+
return ret;
576+
}
577+
504578
/* Bounded recursion through Include directives: a self-including config
505579
* must fail with WS_BAD_ARGUMENT once the depth limit is hit, and the
506580
* config object must remain usable so a subsequent load of a normal
@@ -936,6 +1010,7 @@ const TEST_CASE testCases[] = {
9361010
TEST_DECL(test_ParseConfigLine),
9371011
TEST_DECL(test_ConfigCopy),
9381012
TEST_DECL(test_GetUserConfMatchOverride),
1013+
TEST_DECL(test_MatchUnsupportedSelector),
9391014
TEST_DECL(test_CAKeysFileDiffers),
9401015
TEST_DECL(test_IncludeRecursionBound),
9411016
TEST_DECL(test_ConfigFree),

0 commit comments

Comments
 (0)