Skip to content

Commit 5b84623

Browse files
committed
Introduce decision context
The decision path kept mutable hot-path state spread across file-static globals: subject/object caches and their eviction counters in event.c, the syslog working buffer in policy.c, decision counters in policy-metrics.c, and the defer queue in notify.c. That made the single decision thread behavior implicit and blocked the later per-worker ownership boundary. Add a decision_context that owns those caches, counters, working buffer, and defer queue, allocate one active instance from init_event_system(), and route existing users through that active context. Move decision-defer into the library so the context can own it while preserving the current single-context behavior.
1 parent f128c0c commit 5b84623

9 files changed

Lines changed: 288 additions & 97 deletions

File tree

src/Makefile.am

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ libfapolicyd_la_SOURCES = \
4242
library/database.h \
4343
library/daemon-config.c \
4444
library/daemon-config.h \
45+
library/decision-context.h \
46+
library/decision-defer.c \
47+
library/decision-defer.h \
4548
library/decision-event.h \
4649
library/decision-timing.c \
4750
library/decision-timing.h \
@@ -126,8 +129,6 @@ libfapolicyd_la_CFLAGS = $(fapolicyd_CFLAGS)
126129
libfapolicyd_la_LDFLAGS = $(fapolicyd_LDFLAGS) -lpthread
127130

128131
fapolicyd_SOURCES = \
129-
daemon/decision-defer.c \
130-
daemon/decision-defer.h \
131132
daemon/fanotify-fs-error.c \
132133
daemon/fanotify-fs-error.h \
133134
daemon/fapolicyd.c \

src/daemon/notify.c

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include <time.h>
4242
#include "attr-lookup-metrics.h"
4343
#include "conf.h"
44+
#include "decision-context.h"
4445
#include "decision-defer.h"
4546
#include "decision-timing.h"
4647
#include "failure-action.h"
@@ -65,8 +66,6 @@ extern conf_t config;
6566
static pid_t our_pid;
6667
static struct queue *q = NULL;
6768
static struct queue_metrics last_queue_metrics;
68-
static struct decision_defer_queue defer_queue;
69-
static struct decision_defer_metrics last_defer_metrics;
7069
static pthread_t decision_thread;
7170
static pthread_t deadmans_switch_thread;
7271
static atomic_bool alive = true;
@@ -78,6 +77,9 @@ static unsigned int rpt_interval;
7877
static struct message_rate_limit kernel_queue_overflow_log =
7978
MESSAGE_RATE_LIMIT_INIT(KERNEL_OVERFLOW_LOG_INTERVAL);
8079

80+
#define defer_queue (decision_context_current()->defer_queue)
81+
#define last_defer_metrics (decision_context_current()->last_defer_metrics)
82+
8183
// Local functions
8284
static void *decision_thread_main(void *arg);
8385
static void *deadmans_switch_thread_main(void *arg);
@@ -203,7 +205,8 @@ int init_fanotify(const conf_t *conf, mlist *m)
203205
exit(1);
204206
}
205207
q_metrics_snapshot(q, &last_queue_metrics);
206-
if (decision_defer_init(&defer_queue, conf->subj_cache_size)) {
208+
if (defer_queue.entries == NULL &&
209+
decision_defer_init(&defer_queue, conf->subj_cache_size)) {
207210
msg(LOG_ERR, "Failed setting up subject defer array (%s)",
208211
strerror(errno));
209212
q_close(q);
@@ -239,7 +242,6 @@ int init_fanotify(const conf_t *conf, mlist *m)
239242
if (fd < 0) {
240243
msg(LOG_ERR, "Failed opening fanotify fd (%s)",
241244
strerror(errno));
242-
decision_defer_destroy(&defer_queue);
243245
q_close(q);
244246
q = NULL;
245247
exit(1);
@@ -253,7 +255,6 @@ int init_fanotify(const conf_t *conf, mlist *m)
253255
msg(LOG_ERR, "Failed to create decision thread (%s)",
254256
strerror(rc));
255257
close(fd);
256-
decision_defer_destroy(&defer_queue);
257258
q_close(q);
258259
exit(1);
259260
}
@@ -269,7 +270,6 @@ int init_fanotify(const conf_t *conf, mlist *m)
269270
if (rpt_timer_fd != -1)
270271
close(rpt_timer_fd);
271272
close(fd);
272-
decision_defer_destroy(&defer_queue);
273273
q_close(q);
274274
exit(1);
275275
}
@@ -422,7 +422,6 @@ void shutdown_fanotify(mlist *m)
422422
decision_defer_metrics_snapshot_reset(&defer_queue,
423423
&last_defer_metrics, 0);
424424
decision_timing_set_queue_depth_hooks(NULL, NULL, NULL);
425-
decision_defer_destroy(&defer_queue);
426425
q_close(q);
427426
q = NULL;
428427
close(rpt_timer_fd);

src/library/decision-context.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* decision-context.h - per-decision mutable state
3+
*
4+
* Copyright (c) 2026 Red Hat Inc.
5+
* All Rights Reserved.
6+
*
7+
* This software may be freely redistributed and/or modified under the
8+
* terms of the GNU General Public License as published by the Free
9+
* Software Foundation; either version 2 of the License, or (at your
10+
* option) any later version.
11+
*/
12+
13+
#ifndef DECISION_CONTEXT_HEADER
14+
#define DECISION_CONTEXT_HEADER
15+
16+
#include <stdbool.h>
17+
#include <stdatomic.h>
18+
#include "decision-defer.h"
19+
#include "lru.h"
20+
#include "message.h"
21+
22+
/*
23+
* decision_policy_counters - policy metrics updated by decision processing.
24+
*
25+
* Ruleset generation is deliberately not part of this block because rules can
26+
* be published before the daemon allocates the runtime decision context.
27+
*/
28+
struct decision_policy_counters {
29+
atomic_ulong allowed;
30+
atomic_ulong denied;
31+
atomic_ulong allowed_by_rule;
32+
atomic_ulong allowed_by_fallthrough;
33+
atomic_ulong fallthrough_open;
34+
atomic_ulong fallthrough_execute;
35+
atomic_ulong fallthrough_trusted;
36+
atomic_ulong fallthrough_untrusted;
37+
atomic_ulong fallthrough_trust_unknown;
38+
atomic_ulong fallthrough_executable;
39+
atomic_ulong fallthrough_programmatic;
40+
atomic_ulong fallthrough_sharedlib;
41+
atomic_ulong fallthrough_unknown_ftype;
42+
atomic_ulong fallthrough_other_ftype;
43+
};
44+
45+
/*
46+
* decision_context - mutable state owned by one decision processor.
47+
*
48+
* There is only one active instance today. Keeping the hot-path caches,
49+
* logging buffer, counters, and defer queue behind this object makes the
50+
* ownership boundary explicit before multiple decision workers are added.
51+
*/
52+
struct decision_context {
53+
Queue *subject_cache;
54+
Queue *object_cache;
55+
char *working_buffer;
56+
bool object_cache_warned;
57+
unsigned int early_subject_cache_evictions;
58+
unsigned int building_tracer_evict_count;
59+
unsigned int building_stale_evict_count;
60+
struct message_rate_limit building_tracer_rate_limit;
61+
struct message_rate_limit building_stale_rate_limit;
62+
struct decision_defer_queue defer_queue;
63+
struct decision_defer_metrics last_defer_metrics;
64+
struct decision_policy_counters policy_counters;
65+
};
66+
67+
struct decision_context *decision_context_current(void);
68+
void decision_context_set_current(struct decision_context *ctx);
69+
70+
#endif

src/library/event.c

Lines changed: 130 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <errno.h>
3737

3838
#include "attr-lookup-metrics.h"
39+
#include "decision-context.h"
3940
#include "event.h"
4041
#include "database.h"
4142
#include "decision-timing.h"
@@ -61,16 +62,28 @@
6162
(SUBJECT_BUILDING_STALE_MS * NSEC_PER_MSEC)
6263
#define SUBJECT_BUILDING_LOG_INTERVAL 60
6364

64-
static Queue *subj_cache = NULL;
65-
static Queue *obj_cache = NULL;
66-
static bool obj_cache_warned = false;
67-
static unsigned int early_subj_cache_evictions = 0;
68-
static unsigned int building_tracer_evictions = 0;
69-
static unsigned int building_stale_evictions = 0;
70-
static struct message_rate_limit building_tracer_log =
71-
MESSAGE_RATE_LIMIT_INIT(SUBJECT_BUILDING_LOG_INTERVAL);
72-
static struct message_rate_limit building_stale_log =
73-
MESSAGE_RATE_LIMIT_INIT(SUBJECT_BUILDING_LOG_INTERVAL);
65+
static struct decision_context default_decision_context = {
66+
.building_tracer_rate_limit =
67+
MESSAGE_RATE_LIMIT_INIT(SUBJECT_BUILDING_LOG_INTERVAL),
68+
.building_stale_rate_limit =
69+
MESSAGE_RATE_LIMIT_INIT(SUBJECT_BUILDING_LOG_INTERVAL),
70+
};
71+
static struct decision_context *current_decision_context =
72+
&default_decision_context;
73+
74+
#define subj_cache (current_decision_context->subject_cache)
75+
#define obj_cache (current_decision_context->object_cache)
76+
#define obj_cache_warned (current_decision_context->object_cache_warned)
77+
#define early_subj_cache_evictions \
78+
(current_decision_context->early_subject_cache_evictions)
79+
#define building_tracer_evictions \
80+
(current_decision_context->building_tracer_evict_count)
81+
#define building_stale_evictions \
82+
(current_decision_context->building_stale_evict_count)
83+
#define building_tracer_log \
84+
(current_decision_context->building_tracer_rate_limit)
85+
#define building_stale_log \
86+
(current_decision_context->building_stale_rate_limit)
7487

7588
atomic_bool needs_flush = false;
7689

@@ -88,6 +101,68 @@ struct building_evict_context {
88101
unsigned int event_count;
89102
};
90103

104+
/*
105+
* decision_context_current - return active mutable decision state.
106+
*
107+
* A default empty context is available before the daemon initializes the real
108+
* caches so policy-only tests can still use the logging buffer.
109+
*/
110+
struct decision_context *decision_context_current(void)
111+
{
112+
return current_decision_context;
113+
}
114+
115+
/*
116+
* decision_context_set_current - publish the active decision context.
117+
* @ctx: context to use, or NULL to restore the default empty context.
118+
* Returns nothing.
119+
*/
120+
void decision_context_set_current(struct decision_context *ctx)
121+
{
122+
if (ctx == NULL)
123+
current_decision_context = &default_decision_context;
124+
else
125+
current_decision_context = ctx;
126+
}
127+
128+
/*
129+
* decision_context_init_rate_limits - initialize per-context log throttles.
130+
* @ctx: context to initialize.
131+
* Returns nothing.
132+
*/
133+
static void decision_context_init_rate_limits(struct decision_context *ctx)
134+
{
135+
atomic_init(&ctx->building_tracer_rate_limit.last_log, 0);
136+
ctx->building_tracer_rate_limit.interval =
137+
SUBJECT_BUILDING_LOG_INTERVAL;
138+
atomic_init(&ctx->building_stale_rate_limit.last_log, 0);
139+
ctx->building_stale_rate_limit.interval = SUBJECT_BUILDING_LOG_INTERVAL;
140+
}
141+
142+
/*
143+
* decision_context_init_policy_counters - initialize policy metric counters.
144+
* @counters: policy counter block to initialize.
145+
* Returns nothing.
146+
*/
147+
static void decision_context_init_policy_counters(
148+
struct decision_policy_counters *counters)
149+
{
150+
atomic_init(&counters->allowed, 0);
151+
atomic_init(&counters->denied, 0);
152+
atomic_init(&counters->allowed_by_rule, 0);
153+
atomic_init(&counters->allowed_by_fallthrough, 0);
154+
atomic_init(&counters->fallthrough_open, 0);
155+
atomic_init(&counters->fallthrough_execute, 0);
156+
atomic_init(&counters->fallthrough_trusted, 0);
157+
atomic_init(&counters->fallthrough_untrusted, 0);
158+
atomic_init(&counters->fallthrough_trust_unknown, 0);
159+
atomic_init(&counters->fallthrough_executable, 0);
160+
atomic_init(&counters->fallthrough_programmatic, 0);
161+
atomic_init(&counters->fallthrough_sharedlib, 0);
162+
atomic_init(&counters->fallthrough_unknown_ftype, 0);
163+
atomic_init(&counters->fallthrough_other_ftype, 0);
164+
}
165+
91166
/*
92167
* event_now_ns - read monotonic time for BUILDING age checks.
93168
*
@@ -302,27 +377,47 @@ static void obj_evict_warn(void *unused)
302377
// Return 0 on success and 1 on error
303378
int init_event_system(const conf_t *config)
304379
{
380+
struct decision_context *ctx;
381+
382+
ctx = calloc(1, sizeof(*ctx));
383+
if (ctx == NULL)
384+
return 1;
385+
decision_context_init_rate_limits(ctx);
386+
decision_context_init_policy_counters(&ctx->policy_counters);
387+
305388
/*
306389
* Attach subject_evict_warn so we can see when fast PID turnover
307390
* drops a subject before classification completes. Without all the
308391
* paths collected ld.so can report spurious access denials. A larger
309392
* subj_cache_size lengthens the window and avoids this condition.
310393
*/
311-
subj_cache=init_lru(config->subj_cache_size,
394+
ctx->subject_cache = init_lru(config->subj_cache_size,
312395
(void (*)(void *))subject_clear, "Subject",
313396
(void (*)(void *))subject_evict_warn);
314-
if (!subj_cache)
397+
if (!ctx->subject_cache) {
398+
free(ctx);
315399
return 1;
400+
}
316401

317-
obj_cache = init_lru(config->obj_cache_size,
402+
ctx->object_cache = init_lru(config->obj_cache_size,
318403
(void (*)(void *))object_clear, "Object",
319404
obj_evict_warn);
320-
if (!obj_cache) {
321-
destroy_lru(subj_cache);
322-
subj_cache = NULL;
405+
if (!ctx->object_cache) {
406+
destroy_lru(ctx->subject_cache);
407+
free(ctx);
323408
return 1;
324409
}
325410

411+
if (decision_defer_init(&ctx->defer_queue, config->subj_cache_size)) {
412+
destroy_lru(ctx->subject_cache);
413+
destroy_lru(ctx->object_cache);
414+
free(ctx);
415+
return 1;
416+
}
417+
decision_defer_metrics_snapshot_reset(&ctx->defer_queue,
418+
&ctx->last_defer_metrics, 0);
419+
420+
decision_context_set_current(ctx);
326421
return 0;
327422
}
328423

@@ -350,23 +445,31 @@ static int flush_cache(void)
350445

351446
void destroy_event_system(void)
352447
{
448+
struct decision_context *ctx = current_decision_context;
449+
450+
if (ctx == &default_decision_context)
451+
return;
452+
353453
/* We're intentionally clearing the caches; disable warnings */
354-
if (subj_cache)
355-
subj_cache->evict_cb = NULL;
356-
if (early_subj_cache_evictions)
454+
if (ctx->subject_cache)
455+
ctx->subject_cache->evict_cb = NULL;
456+
if (ctx->early_subject_cache_evictions)
357457
msg(LOG_WARNING,
358458
"Processes are being evicted from the subject cache before "
359459
"pattern detection completes: increase subj_cache_size "
360-
"(total early evictions: %u)", early_subj_cache_evictions);
361-
if (obj_cache)
362-
obj_cache->evict_cb = NULL;
363-
if (obj_cache_warned)
460+
"(total early evictions: %u)",
461+
ctx->early_subject_cache_evictions);
462+
if (ctx->object_cache)
463+
ctx->object_cache->evict_cb = NULL;
464+
if (ctx->object_cache_warned)
364465
msg(LOG_WARNING,
365466
"object cache eviction ratios high: increase obj_cache_size");
366-
destroy_lru(subj_cache);
367-
destroy_lru(obj_cache);
368-
subj_cache = NULL;
369-
obj_cache = NULL;
467+
destroy_lru(ctx->subject_cache);
468+
destroy_lru(ctx->object_cache);
469+
decision_defer_destroy(&ctx->defer_queue);
470+
free(ctx->working_buffer);
471+
decision_context_set_current(NULL);
472+
free(ctx);
370473
}
371474

372475
static inline void reset_subject_attributes(s_array *s)

0 commit comments

Comments
 (0)