From 77429022a8c8d1ad880374512781f44c53820be1 Mon Sep 17 00:00:00 2001 From: Dhanraj E Date: Sun, 25 Oct 2026 16:41:54 +0530 Subject: [PATCH] cf-serverd: Fixed classic CONTEXT reply including non-admitted classes ContextAccessControl() answers the classic CONTEXT/SCONTEXT request (sent by remoteclassesmatching()) by checking every persistent class that matches the client's regex against the access rules. The access flag was declared once for the whole loop and never reset, so after one class was admitted by a resource_type => "context" rule, every later matching class was returned as well, including classes that no rule admits. The TLS CONTEXT handler already checks each class on its own via acl_CheckRegex(). Changelog: Title Signed-off-by: Dhanraj E --- cf-serverd/server_classic.c | 4 +- tests/unit/protocol_test.c | 76 ++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/cf-serverd/server_classic.c b/cf-serverd/server_classic.c index 8fc439cb10b..5fa1194aefa 100644 --- a/cf-serverd/server_classic.c +++ b/cf-serverd/server_classic.c @@ -406,7 +406,6 @@ static int LiteralAccessControl(EvalContext *ctx, char *in, ServerConnectionStat static Item *ContextAccessControl(EvalContext *ctx, char *in, ServerConnectionState *conn, int encrypt) { Auth *ap; - int access = false; char client_regex[CF_BUFSIZE]; Item *ip, *matches = NULL; @@ -422,6 +421,9 @@ static Item *ContextAccessControl(EvalContext *ctx, char *in, ServerConnectionSt /* Does the class match the regex that the agent requested? */ if (StringMatchFull(client_regex, ip->name)) { + /* Each class must be granted by its own access rule. */ + int access = false; + for (ap = SERVER_ACCESS.varadmit; ap != NULL; ap = ap->next) { /* Does the class match any of the regex in ACLs? */ diff --git a/tests/unit/protocol_test.c b/tests/unit/protocol_test.c index 29b30d31a5d..549950e0f2c 100644 --- a/tests/unit/protocol_test.c +++ b/tests/unit/protocol_test.c @@ -5,9 +5,36 @@ #include #include #include +#include +#include +#include /* xsnprintf */ #include /* GetCommandClassic */ +char CFWORKDIR[CF_BUFSIZE]; + +void tests_setup(void) +{ + static char env[] = /* Needs to be static for putenv() */ + "CFENGINE_TEST_OVERRIDE_WORKDIR=/tmp/CFENGINE_protocol_test.XXXXXX"; + char *workdir = strchr(env, '='); + assert(workdir && workdir[1] == '/'); + workdir++; + + mkdtemp(workdir); + strlcpy(CFWORKDIR, workdir, CF_BUFSIZE); + putenv(env); + + mkdir(GetStateDir(), 0766); +} + +void tests_teardown(void) +{ + char cmd[CF_BUFSIZE]; + xsnprintf(cmd, CF_BUFSIZE, "rm -rf '%s'", CFWORKDIR); + system(cmd); +} + /* * The protocol consists of the following commands: @@ -450,14 +477,59 @@ static void test_user_name(void) assert_true(IsUserNameValid(valid_user_name)); } +static void test_context_access_control_per_class(void) +{ + /* Two persistent classes, only "zz_allowed" is admitted to the client. */ + { + CF_DB *dbp; + PersistentClassInfo info = { .expires = UINT_MAX, + .policy = CONTEXT_STATE_POLICY_PRESERVE }; + assert_true(OpenDB(&dbp, dbid_state)); + assert_true(WriteDB(dbp, "aa_denied", &info, sizeof(info))); + assert_true(WriteDB(dbp, "zz_allowed", &info, sizeof(info))); + CloseDB(dbp); + } + + Auth *ap = xcalloc(1, sizeof(Auth)); + ap->path = xstrdup("zz_allowed"); + ap->classpattern = true; + PrependItem(&ap->accesslist, "127.0.0.1", NULL); + SERVER_ACCESS.varadmit = ap; + + ServerConnectionState conn = { 0 }; + strlcpy(conn.ipaddr, "127.0.0.1", sizeof(conn.ipaddr)); + strlcpy(conn.hostname, "client.example.com", sizeof(conn.hostname)); + + EvalContext *ctx = EvalContextNew(); + char request[] = "CONTEXT .*"; + Item *classes = ContextAccessControl(ctx, request, &conn, false); + + assert_true(IsItemIn(classes, "zz_allowed")); + assert_false(IsItemIn(classes, "aa_denied")); + + DeleteItemList(classes); + EvalContextDestroy(ctx); + SERVER_ACCESS.varadmit = NULL; + DeleteItemList(ap->accesslist); + free(ap->path); + free(ap); +} + int main() { PRINT_TEST_BANNER(); + tests_setup(); + const UnitTest tests[] = { unit_test(test_command_parser), - unit_test(test_user_name) + unit_test(test_user_name), + unit_test(test_context_access_control_per_class) }; - return run_tests(tests); + int ret = run_tests(tests); + + tests_teardown(); + + return ret; }