Skip to content

Commit b947e68

Browse files
committed
Return library reload failures to daemon control
Recoverable library errors in trust reload and RPM backend loader paths could terminate the daemon from inside src/library. Runtime backend reload failures also closed the live trust database, which risked losing the previous generation instead of leaving reload failure handling under daemon control. The reload path now returns explicit errors, records trust reload failures, preserves or reopens the previous LMDB generation on reload sizing failures, and keeps the active database open when runtime backend init/load fails. The RPM loader parent now returns backend load failure for socket and loader IPC failures instead of calling exit(), while documented LRU aborts remain limited to cache corruption invariants. Tests add fault coverage for libmagic initialization failure, trust reload failure preservation, and RPM loader IPC failure.
1 parent 3f994d3 commit b947e68

11 files changed

Lines changed: 292 additions & 32 deletions

File tree

src/library/database.c

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5516,12 +5516,12 @@ static void finish_trust_database_reload(void)
55165516
atomic_store_explicit(&reload_db_active, false, memory_order_release);
55175517
}
55185518

5519-
static void do_reload_db(conf_t* config)
5519+
static int do_reload_db(conf_t *config)
55205520
{
55215521
msg(LOG_INFO,
55225522
"It looks like there was an update of the system... Syncing DB.");
55235523

5524-
int rc;
5524+
int rc = 0;
55255525
unsigned int old_db_max_size = config->db_max_size;
55265526
unsigned int old_reload_floor_mb = autosize_reload_floor_mb;
55275527

@@ -5534,30 +5534,54 @@ static void do_reload_db(conf_t* config)
55345534
config->db_max_size);
55355535

55365536
if (config->db_max_size < old_db_max_size) {
5537+
unsigned long old_trust_generation;
5538+
unsigned long old_env_generation;
5539+
55375540
/*
55385541
* This is a map-size reopen, not controlled
55395542
* compaction. If high-water usage needs physical file
55405543
* rewrite, status reports recommend the explicit admin
55415544
* compaction command instead of hiding a swap in reload.
55425545
*/
5546+
old_trust_generation =
5547+
trust_db_current_generation_number();
5548+
lmdb_environment_snapshot(&old_env_generation, NULL);
5549+
if (old_env_generation == 0)
5550+
old_env_generation = 1;
5551+
55435552
lock_update_thread();
5544-
close_env(0);
5553+
close_env(1);
5554+
5555+
rc = init_db_with_generations(config,
5556+
old_trust_generation, old_env_generation);
5557+
if (rc) {
5558+
int reopen_rc;
55455559

5546-
rc = init_db(config);
5560+
msg(LOG_ERR,
5561+
"Cannot open the resized trust database, init_db() (%d)",
5562+
rc);
5563+
config->db_max_size = old_db_max_size;
5564+
autosize_reload_floor_mb = old_reload_floor_mb;
5565+
reopen_rc = init_db_with_generations(config,
5566+
old_trust_generation,
5567+
old_env_generation);
5568+
if (reopen_rc) {
5569+
msg(LOG_ERR,
5570+
"Cannot reopen previous trust database, init_db() (%d)",
5571+
reopen_rc);
5572+
rc = reopen_rc;
5573+
} else
5574+
msg(LOG_ERR,
5575+
"Previous trust database preserved after reload sizing failure");
5576+
}
55475577
unlock_update_thread();
55485578

55495579
if (rc) {
5550-
msg(LOG_ERR,
5551-
"Cannot open the trust database, init_db() (%d)",
5552-
rc);
55535580
if (stop)
55545581
goto out;
55555582

55565583
record_trust_reload_failure();
5557-
close(ffd[0].fd);
5558-
backend_close();
5559-
unlink_fifo();
5560-
exit(rc);
5584+
goto out;
55615585
}
55625586
} else if (config->db_max_size > old_db_max_size) {
55635587
lock_update_thread();
@@ -5579,14 +5603,12 @@ static void do_reload_db(conf_t* config)
55795603
if ((rc = backend_init(config))) {
55805604
msg(LOG_ERR, "Failed to load trust data from backend (%d)", rc);
55815605
record_trust_reload_failure();
5582-
close_db(0);
55835606
goto out;
55845607
}
55855608

55865609
if ((rc = backend_load(config))) {
55875610
msg(LOG_ERR, "Failed to load data from backend (%d)", rc);
55885611
record_trust_reload_failure();
5589-
close_db(0);
55905612
goto out;
55915613
}
55925614

@@ -5598,24 +5620,33 @@ static void do_reload_db(conf_t* config)
55985620

55995621
record_trust_reload_failure();
56005622
if (rc == UPDATE_DB_PRESERVED) {
5601-
// update_database() failed before clearing the live DB.
5602-
// Keep running with the last successfully built trust set.
56035623
msg(LOG_ERR,
56045624
"Previous trust database preserved after reload failure");
56055625
goto out;
56065626
}
56075627

5608-
close(ffd[0].fd);
5609-
backend_close();
5610-
unlink_fifo();
5611-
exit(rc);
5628+
msg(LOG_ERR,
5629+
"Trust database reload failed with active DB under daemon control");
5630+
goto out;
56125631
}
56135632

56145633
msg(LOG_INFO, "Updated");
56155634

56165635
out:
56175636
// Conserve memory
56185637
backend_close();
5638+
return rc;
5639+
}
5640+
5641+
/*
5642+
* database_reload_for_tests - run the trust reload path directly.
5643+
* @config: test configuration containing backend and LMDB settings.
5644+
*
5645+
* Returns 0 on success and non-zero when reload failed.
5646+
*/
5647+
int database_reload_for_tests(conf_t *config)
5648+
{
5649+
return do_reload_db(config);
56195650
}
56205651

56215652
/*

src/library/database.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ int database_publish_memfd_for_tests(int memfd, conf_t *config)
9292
int database_publish_startup_memfd_for_tests(int memfd, conf_t *config)
9393
__nonnull ((2));
9494
int database_drop_candidate_after_import_for_tests(int memfd);
95+
int database_reload_for_tests(conf_t *config) __nonnull ((1));
9596
int database_compact_memfd_for_tests(int memfd, conf_t *config)
9697
__nonnull ((2));
9798
void *database_generation_hold_for_tests(void);

src/library/fapolicyd-backend.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,9 @@ typedef struct _backend
5151
long entries;
5252
} backend;
5353

54+
#ifdef USE_RPM
55+
int rpm_backend_load_from_path_for_tests(const conf_t *conf,
56+
const char *loader_path) __nonnull ((1, 2));
57+
#endif
58+
5459
#endif

src/library/file.c

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ static char *get_program_cwd_from_pid(pid_t pid, size_t blen, char *buf)
6060
__attr_access ((__write_only__, 3, 2));
6161
static void resolve_path(const char *pcwd, char *path, size_t len)
6262
__attr_access ((__write_only__, 2, 3));
63+
static file_init_status_t file_init_failure;
6364

6465
/*
6566
* file_close_context - release file helper state owned by a decision context.
@@ -123,6 +124,12 @@ int file_init(void)
123124

124125
// Setup libmagic
125126
unsetenv("MAGIC");
127+
if (file_init_failure == FILE_INIT_MAGIC_FAST_OPEN_FAILED) {
128+
msg(LOG_ERR, "Unable to init fast libmagic");
129+
file_close_context(ctx);
130+
return FILE_INIT_MAGIC_FAST_OPEN_FAILED;
131+
}
132+
126133
// Fast magic: minimal rules, all expensive checks disabled
127134
ctx->magic_fast = magic_open(
128135
MAGIC_MIME |
@@ -136,33 +143,48 @@ int file_init(void)
136143
MAGIC_NO_CHECK_JSON /* Skip JSON validation */
137144
);
138145
if (ctx->magic_fast == NULL) {
139-
msg(LOG_ERR, "Unable to init libmagic");
146+
msg(LOG_ERR, "Unable to init fast libmagic");
140147
file_close_context(ctx);
141-
return 1;
148+
return FILE_INIT_MAGIC_FAST_OPEN_FAILED;
142149
}
143150

144151
// Load only essential magic rules
152+
if (file_init_failure == FILE_INIT_MAGIC_FAST_LOAD_FAILED) {
153+
msg(LOG_ERR, "Unable to load fast magic database");
154+
file_close_context(ctx);
155+
return FILE_INIT_MAGIC_FAST_LOAD_FAILED;
156+
}
145157
if (magic_load(ctx->magic_fast, MAGIC_PATH) != 0) {
146158
msg(LOG_ERR, "Unable to load fast magic database");
147159
file_close_context(ctx);
148-
return 2;
160+
return FILE_INIT_MAGIC_FAST_LOAD_FAILED;
149161
}
150162

151163
// Full magic: normal operation
164+
if (file_init_failure == FILE_INIT_MAGIC_FULL_OPEN_FAILED) {
165+
msg(LOG_ERR, "Unable to init full libmagic");
166+
file_close_context(ctx);
167+
return FILE_INIT_MAGIC_FULL_OPEN_FAILED;
168+
}
152169
ctx->magic_full = magic_open(MAGIC_MIME|MAGIC_ERROR|MAGIC_NO_CHECK_CDF|
153170
MAGIC_NO_CHECK_ELF);
154171
if (ctx->magic_full == NULL) {
155-
msg(LOG_ERR, "Unable to init libmagic");
172+
msg(LOG_ERR, "Unable to init full libmagic");
156173
file_close_context(ctx);
157-
return 3;
174+
return FILE_INIT_MAGIC_FULL_OPEN_FAILED;
158175
}
159176
// System default
177+
if (file_init_failure == FILE_INIT_MAGIC_FULL_LOAD_FAILED) {
178+
msg(LOG_ERR, "Unable to load default magic database");
179+
file_close_context(ctx);
180+
return FILE_INIT_MAGIC_FULL_LOAD_FAILED;
181+
}
160182
if (magic_load(ctx->magic_full, NULL) != 0) {
161183
msg(LOG_ERR, "Unable to load default magic database");
162184
file_close_context(ctx);
163-
return 4;
185+
return FILE_INIT_MAGIC_FULL_LOAD_FAILED;
164186
}
165-
return 0;
187+
return FILE_INIT_OK;
166188
}
167189

168190

@@ -173,6 +195,18 @@ void file_close(void)
173195
}
174196

175197

198+
/*
199+
* file_init_failure_for_tests - request one file_init() fault injection.
200+
* @failure: file_init_status_t value to return, or FILE_INIT_OK to disable.
201+
*
202+
* Returns nothing.
203+
*/
204+
void file_init_failure_for_tests(file_init_status_t failure)
205+
{
206+
file_init_failure = failure;
207+
}
208+
209+
176210
/*
177211
* file_hash_length - return the binary digest size for the algorithm.
178212
* @alg: file digest algorithm to query.

src/library/file.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ typedef enum {
3939
FILE_HASH_ALG_SHA512,
4040
} file_hash_alg_t;
4141

42+
typedef enum {
43+
FILE_INIT_OK = 0,
44+
FILE_INIT_MAGIC_FAST_OPEN_FAILED = 1,
45+
FILE_INIT_MAGIC_FAST_LOAD_FAILED = 2,
46+
FILE_INIT_MAGIC_FULL_OPEN_FAILED = 3,
47+
FILE_INIT_MAGIC_FULL_LOAD_FAILED = 4,
48+
} file_init_status_t;
49+
4250
#define MD5_LEN 16
4351
#define SHA1_LEN 20
4452
#define SHA256_LEN 32
@@ -63,6 +71,7 @@ struct file_info
6371
};
6472

6573
int file_init(void);
74+
void file_init_failure_for_tests(file_init_status_t failure);
6675
void file_close(void);
6776
struct file_info *stat_file_entry(int fd) __attr_dealloc_free;
6877
void file_info_reset_digest(struct file_info *info);

src/library/lru.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ static void remove_node(Queue *queue, const QNode *node)
270270
goto out;
271271
} else {
272272
if (node->prev->next != node) {
273+
/*
274+
* The LRU cache owns enforcement objects. Once neighbor
275+
* links disagree, continuing can detach or free the
276+
* wrong object, so this remains an invariant failure.
277+
*/
273278
msg(LOG_ERR, "Linked list corruption detected %s",
274279
queue->name);
275280
abort();
@@ -284,6 +289,11 @@ static void remove_node(Queue *queue, const QNode *node)
284289
queue->end->next = NULL;
285290
} else {
286291
if (node->next->prev != node) {
292+
/*
293+
* The LRU cache owns enforcement objects. Once neighbor
294+
* links disagree, continuing can detach or free the
295+
* wrong object, so this remains an invariant failure.
296+
*/
287297
msg(LOG_ERR, "Linked List corruption detected %s",
288298
queue->name);
289299
abort();
@@ -339,6 +349,11 @@ void lru_evict(Queue *queue, unsigned int key)
339349
QNode *temp = queue->front;
340350

341351
if (hash->array[key] != temp) {
352+
/*
353+
* Key-to-node mismatch means the hash table and eviction list
354+
* no longer describe the same cache. Evicting after that could
355+
* corrupt enforcement state, so stop at the invariant failure.
356+
*/
342357
msg(LOG_ERR, "lru_evict called with mismatched key %s",
343358
queue->name);
344359
abort();

src/library/rpm-backend.c

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,15 @@ struct _hash_record {
217217

218218
#define BUFFER_SIZE 4096
219219
#define MAX_DELIMS 3 // Trustdb has 4 fields - therefore 3 delimiters
220+
static const char *rpm_loader_path = BINARYDIR "/fapolicyd-rpm-loader";
221+
220222
static int rpm_load_list(const conf_t *conf)
221223
{
222224
// before the spawn
223225
int sv[2];
224226
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv) < 0) {
225227
msg(LOG_ERR, "socketpair failed");
226-
exit(1);
228+
return 1;
227229
}
228230

229231
posix_spawn_file_actions_t actions;
@@ -238,7 +240,7 @@ static int rpm_load_list(const conf_t *conf)
238240
char *custom_env[] = { "FAPO_SOCK_FD=3", NULL };
239241

240242
pid_t pid = -1;
241-
int status = posix_spawn(&pid, BINARYDIR "/fapolicyd-rpm-loader",
243+
int status = posix_spawn(&pid, rpm_loader_path,
242244
&actions, NULL, argv, custom_env);
243245
close(sv[1]); // Parent doesn't write
244246

@@ -258,15 +260,20 @@ static int rpm_load_list(const conf_t *conf)
258260
_msg.msg_controllen = sizeof cmsgbuf.buf;
259261

260262
if (recvmsg(sv[0], &_msg, 0) < 0) {
261-
msg(LOG_ERR, "recvmesg failed");
262-
exit(1);
263+
msg(LOG_ERR, "recvmsg failed");
264+
close(sv[0]);
265+
waitpid(pid, &status, 0);
266+
posix_spawn_file_actions_destroy(&actions);
267+
return 1;
263268
}
264269
close(sv[0]);
265270

266271
struct cmsghdr *c = CMSG_FIRSTHDR(&_msg);
267272
if (!c || c->cmsg_type != SCM_RIGHTS) {
268273
msg(LOG_ERR, "missing fd");
269-
exit(1);
274+
waitpid(pid, &status, 0);
275+
posix_spawn_file_actions_destroy(&actions);
276+
return 1;
270277
}
271278

272279
int memfd;
@@ -286,8 +293,10 @@ static int rpm_load_list(const conf_t *conf)
286293
rpm_backend.memfd = memfd;
287294

288295
waitpid(pid, &status, 0);
289-
} else
296+
} else {
297+
close(sv[0]);
290298
msg(LOG_ERR, "posix_spawn failed: %s\n", strerror(status));
299+
}
291300

292301
posix_spawn_file_actions_destroy(&actions);
293302

@@ -305,6 +314,25 @@ static int rpm_load_list(const conf_t *conf)
305314
return exit_rc;
306315
}
307316

317+
/*
318+
* rpm_backend_load_from_path_for_tests - exercise rpm loader IPC errors.
319+
* @conf: test configuration.
320+
* @loader_path: helper executable path to spawn.
321+
*
322+
* Returns the rpm backend load result without terminating the caller.
323+
*/
324+
int rpm_backend_load_from_path_for_tests(const conf_t *conf,
325+
const char *loader_path)
326+
{
327+
const char *previous_loader_path = rpm_loader_path;
328+
int rc;
329+
330+
rpm_loader_path = loader_path;
331+
rc = rpm_load_list(conf);
332+
rpm_loader_path = previous_loader_path;
333+
return rc;
334+
}
335+
308336
// this function is used in fapolicyd-rpm-loader
309337
extern unsigned int debug_mode;
310338
int do_rpm_load_list(const conf_t *conf, int memfd)

0 commit comments

Comments
 (0)