fix(daemon-conf): reject empty hosts/auth allow-lists (C4)
A present hosts allow/hosts deny/auth users key with an empty or separator-only value produced a zero-length list, silently meaning no ACL / no auth and contradicting the strict-parse contract. store_host_list and the auth users parser now track how many entries a present key actually added and fail the load with a clear error when it is zero, so a restrictive directive can never silently become open. Unit tests cover empty, whitespace-only and comma-only values.
This commit is contained in:
@@ -391,6 +391,22 @@ static void test_daemon_conf_auth_users_validated() {
|
||||
EXPECT_EQ_STR(ok_conf->modules[0].auth_users[0], "alice");
|
||||
EXPECT_EQ_STR(ok_conf->modules[0].auth_users[1], "bob");
|
||||
daemon_conf_free(ok_conf);
|
||||
|
||||
/* C4: an empty or separator-only `auth users` value is a parse error. It
|
||||
* would otherwise leave the module with a zero-length allow-list, silently
|
||||
* disabling the authentication the operator asked for. */
|
||||
const char* empty_auth[] = {
|
||||
"[m]\npath = /x\nauth users = \n",
|
||||
"[m]\npath = /x\nauth users = , ,\n",
|
||||
"[m]\npath = /x\nauth users = \t\n",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(empty_auth) / sizeof(empty_auth[0]); i++) {
|
||||
EXPECT_EQ_INT(write_conf(empty_auth[i], &path), 0);
|
||||
const DaemonConf* rejected = daemon_conf_load(path, err, sizeof(err));
|
||||
free(path);
|
||||
EXPECT_NULL(rejected);
|
||||
EXPECT_TRUE(strstr(err, "'auth users' must list at least one user") != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wave 3 daemon hardening: configurable global/per-module connection caps,
|
||||
@@ -475,13 +491,33 @@ static void test_daemon_conf_limits_and_hosts_parse() {
|
||||
EXPECT_EQ_INT(conf->modules[0].max_connections, 0);
|
||||
daemon_conf_free(conf);
|
||||
|
||||
/* An empty hosts list is not an error (no patterns are added). */
|
||||
EXPECT_EQ_INT(write_conf("hosts allow = \n[m]\npath = /x\n", &path), 0);
|
||||
conf = daemon_conf_load(path, err, sizeof(err));
|
||||
free(path);
|
||||
EXPECT_NOT_NULL(conf);
|
||||
EXPECT_EQ_INT(conf->global.hosts_allow_count, 0);
|
||||
daemon_conf_free(conf);
|
||||
/* C4: a present hosts key with an empty/separator-only value must not silently
|
||||
* install a zero-length (allow-everyone) list. */
|
||||
const char* empty_hosts[] = {
|
||||
"hosts allow = \n[m]\npath = /x\n",
|
||||
"hosts deny = \n[m]\npath = /x\n",
|
||||
"hosts allow = , ,\n[m]\npath = /x\n",
|
||||
"hosts deny = \t\n[m]\npath = /x\n",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(empty_hosts) / sizeof(empty_hosts[0]); i++) {
|
||||
EXPECT_EQ_INT(write_conf(empty_hosts[i], &path), 0);
|
||||
const DaemonConf* rejected = daemon_conf_load(path, err, sizeof(err));
|
||||
free(path);
|
||||
EXPECT_NULL(rejected);
|
||||
EXPECT_TRUE(strstr(err, "must list at least one host pattern") != NULL);
|
||||
}
|
||||
|
||||
const char* empty_module_hosts[] = {
|
||||
"[m]\npath = /x\nhosts allow = \n",
|
||||
"[m]\npath = /x\nhosts deny = ,\n",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(empty_module_hosts) / sizeof(empty_module_hosts[0]); i++) {
|
||||
EXPECT_EQ_INT(write_conf(empty_module_hosts[i], &path), 0);
|
||||
const DaemonConf* rejected = daemon_conf_load(path, err, sizeof(err));
|
||||
free(path);
|
||||
EXPECT_NULL(rejected);
|
||||
EXPECT_TRUE(strstr(err, "must list at least one host pattern") != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_daemon_hosts_allowed() {
|
||||
|
||||
Reference in New Issue
Block a user