zpmod  b19981f
High-performance Zsh module for script optimization and filesystem helpers
compaudit_cache.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: MIT */
17 // NOLINTBEGIN(misc-include-cleaner)
18 /* Canonical module header ordering: include gateway first */
19 #include "zpmod.mdh"
20 #include "zpmod.pro"
21 /* System headers after gateway */
22 #include "zpmod_compaudit.h"
23 #include "zpmod_emoji.h"
24 #include <dirent.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <time.h>
34 #include <unistd.h>
35 
36 /* Portable fallback for platforms that don't define PATH_MAX in <limits.h>
37  * (e.g., certain SDK configurations). Use a conservative default. */
38 #ifndef PATH_MAX
39 #define PATH_MAX 4096
40 #endif
41 
42 #define ZP_COMPAUDIT_CACHE_SUBDIR "zpmod"
43 #define ZP_COMPAUDIT_CACHE_FILE "compaudit_v3.zcache"
44 
45 /* -------- Helpers -------- */
46 
47 /* Choose XDG cache dir or fallback to ~/.cache; return newly allocated string.
48  */
49 static char *zp_cc_base_cache_dir(void) {
50  const char *xdg = getsparam("XDG_CACHE_HOME");
51  const char *home = getsparam("HOME");
52  const char *base = (xdg && *xdg) ? xdg : NULL;
53  if (!base && home && *home) {
54  size_t len = strlen(home) + sizeof("/.cache");
55  char *p = (char *)zalloc(len);
56  if (!p) {
57  return NULL;
58  }
59  strcpy(p, home);
60  strcat(p, "/.cache");
61  return p; /* caller frees */
62  }
63  if (base) {
64  return ztrdup(base);
65  }
66  return NULL;
67 }
68 
69 /* Ensure zpmod cache directory exists (0700). Returns allocated full dir path
70  * or NULL. */
71 static char *zp_cc_ensure_dir(char *nam) {
72  char *base = zp_cc_base_cache_dir();
73  if (!base) {
74  zwarnnam(nam, "%scompaudit-cache: cannot resolve cache directory (no HOME)",
75  zp_icon("❌ "));
76  return NULL;
77  }
78  /* append /zpmod */
79  size_t len = strlen(base) + 1 + strlen(ZP_COMPAUDIT_CACHE_SUBDIR) + 1;
80  char *full = (char *)zalloc(len);
81  if (!full) {
82  zsfree(base);
83  return NULL;
84  }
85  sprintf(full, "%s/%s", base, ZP_COMPAUDIT_CACHE_SUBDIR);
86  struct stat st;
87  if (stat(full, &st) != 0) {
88  if (mkdir(full, 0700) != 0) {
89  zwarnnam(nam, "%scompaudit-cache: cannot create %s: %s", zp_icon("❌ "),
90  full, strerror(errno));
91  zsfree(base);
92  zsfree(full);
93  return NULL;
94  }
95  } else {
96  if (!S_ISDIR(st.st_mode)) {
97  zwarnnam(nam, "%scompaudit-cache: %s exists and is not a directory",
98  zp_icon("❌ "), full);
99  zsfree(base);
100  zsfree(full);
101  return NULL;
102  }
103  }
104  zsfree(base);
105  return full;
106 }
107 
108 static char *zp_cc_cache_file_path(char *nam) {
109  char *dir = zp_cc_ensure_dir(nam);
110  if (!dir) {
111  return NULL;
112  }
113  size_t len = strlen(dir) + 1 + strlen(ZP_COMPAUDIT_CACHE_FILE) + 1;
114  char *p = (char *)zalloc(len);
115  if (!p) {
116  zsfree(dir);
117  return NULL;
118  }
119  sprintf(p, "%s/%s", dir, ZP_COMPAUDIT_CACHE_FILE);
120  zsfree(dir);
121  return p;
122 }
123 
124 struct zp_cc_entry {
125  char *path;
126  int verdict;
127  mode_t mode;
128  uid_t uid;
129  gid_t gid;
130  time_t mtime;
131  time_t ctime;
134 };
135 
136 /* forward */
137 static int parent_insecure_path(const char *path, uid_t self);
138 
139 /* Very small vector for entries */
140 struct zp_cc_vec {
142  size_t size;
143  size_t cap;
144 };
145 static void zp_cc_vec_init(struct zp_cc_vec *v) {
146  v->items = NULL;
147  v->size = 0;
148  v->cap = 0;
149 }
150 static int zp_cc_vec_push(struct zp_cc_vec *v, struct zp_cc_entry *e) {
151  if (v->size == v->cap) {
152  size_t ncap = v->cap ? v->cap * 2 : 8;
153  void *nbuf = zrealloc(v->items, ncap * sizeof(struct zp_cc_entry));
154  if (!nbuf) {
155  return 1;
156  }
157  v->items = (struct zp_cc_entry *)nbuf;
158  v->cap = ncap;
159  }
160  v->items[v->size++] = *e;
161  return 0;
162 }
163 static void zp_cc_vec_free(struct zp_cc_vec *v) {
164  if (!v) {
165  return;
166  }
167  for (size_t i = 0; i < v->size; i++) {
168  if (v->items[i].path) {
169  zsfree(v->items[i].path);
170  }
171  }
172  if (v->items) {
173  zfree(v->items, v->cap * sizeof(struct zp_cc_entry));
174  }
175  v->items = NULL;
176  v->size = v->cap = 0;
177 }
178 
179 /*
180  * Security verdict parity slice:
181  * - Treat directory insecure if:
182  * * owned by someone other than allowed owners (root, EUID, zsh exe owner
183  * if detectable) AND (world-writable and not sticky) OR (group-writable and not
184  * sticky)
185  * * OR it is a symlink whose final target satisfies above (stat() already
186  * resolves)
187  * - Sticky bit (S_ISVTX) on a world/group writable dir neutralizes that
188  * writable bit (like /tmp semantics)
189  * - Root/EUID/exe-owner owned dirs are considered secure unless world-writable
190  * without sticky (defensive)
191  */
192 static uid_t zp_cached_exe_owner = (uid_t)-1;
193 static void zp_cc_discover_exe_owner(void) {
194  if (zp_cached_exe_owner != (uid_t)-1) {
195  return;
196  }
197  const char *candidates[] = {"/proc/self/exe", NULL};
198  for (int cand_index = 0; candidates[cand_index]; ++cand_index) {
199  struct stat st;
200  if (stat(candidates[cand_index], &st) == 0) {
201  zp_cached_exe_owner = st.st_uid;
202  return;
203  }
204  }
205  zp_cached_exe_owner = 0; /* fallback root */
206 }
207 static int zp_cc_owner_allowed(uid_t uid, uid_t self) {
208  if (uid == 0 || uid == self) {
209  return 1;
210  }
211  if (zp_cached_exe_owner != (uid_t)-1 && uid == zp_cached_exe_owner) {
212  return 1;
213  }
214  return 0;
215 }
216 static int zp_cc_is_insecure(struct stat *st, uid_t self) {
217  if (!st) {
218  return 1;
219  }
221  int sticky = (st->st_mode & S_ISVTX) ? 1 : 0;
222  int world_w = (st->st_mode & S_IWOTH) ? 1 : 0;
223  int group_w = (st->st_mode & S_IWGRP) ? 1 : 0;
224  int allowed_owner = zp_cc_owner_allowed(st->st_uid, self);
225  if (!allowed_owner) {
226  if ((world_w && !sticky) || (group_w && !sticky)) {
227  return 1;
228  }
229  } else {
230  /* even if owner allowed, treat truly open world-writable non-sticky as
231  * insecure */
232  if (world_w && !sticky) {
233  return 1;
234  }
235  }
236  return 0;
237 }
238 
239 /* Collect target directories (initial slice: $fpath elements). */
240 static void zp_cc_collect_dirs(struct zp_cc_vec *out) {
241  char **arr = getaparam("fpath");
242  if (!arr) {
243  return;
244  }
245  for (char **fpath_it = arr; *fpath_it; ++fpath_it) {
246  if (!**fpath_it) {
247  continue;
248  }
249  size_t len = strlen(*fpath_it) + 1;
250  char *dup = (char *)zalloc(len);
251  if (!dup) {
252  break;
253  }
254  memcpy(dup, *fpath_it, len);
255  struct zp_cc_entry e;
256  e.path = dup;
257  e.verdict = 0;
258  e.mode = 0;
259  e.uid = 0;
260  e.gid = 0;
261  e.mtime = 0;
262  e.ctime = 0;
263  if (zp_cc_vec_push(out, &e)) {
264  zsfree(dup);
265  break;
266  }
267  }
268 }
269 
270 /* Load cache into a simple vector; returns 0 success, 1 error. */
271 static int zp_cc_load_cache(const char *path, struct zp_cc_vec *out) {
272  FILE *fp = fopen(path, "r");
273  if (!fp) {
274  return 1;
275  }
276  char line[4096];
277  if (!fgets(line, sizeof(line), fp)) {
278  fclose(fp);
279  return 1;
280  }
281  if (strncmp(line, "version:3", 9) != 0) {
282  fclose(fp);
283  return 1;
284  }
285  while (fgets(line, sizeof(line), fp)) {
286  if (line[0] == '\n' || line[0] == '#') {
287  continue;
288  }
289  char *nl = strchr(line, '\n');
290  if (nl) {
291  *nl = '\0';
292  }
293  char *save = NULL;
294  char *tok = strtok_r(line, "\t", &save);
295  if (!tok) {
296  continue;
297  }
298  char *path_dup = ztrdup(tok);
299  int fld = 0;
300  long fields[8]; /* verdict, mode(octal), uid, gid, mtime, ctime,
301  parent_insecure, zwc_insecure */
302  while (fld < 8 && (tok = strtok_r(NULL, "\t", &save))) {
303  int base = (fld == 1) ? 8 : 10;
304  fields[fld] = strtol(tok, NULL, base);
305  fld++;
306  }
307  if (fld != 8) {
308  if (path_dup) {
309  zsfree(path_dup);
310  }
311  continue;
312  }
313  struct zp_cc_entry e;
314  e.path = path_dup;
315  e.verdict = (int)fields[0];
316  e.mode = (mode_t)fields[1];
317  e.uid = (uid_t)fields[2];
318  e.gid = (gid_t)fields[3];
319  e.mtime = (time_t)fields[4];
320  e.ctime = (time_t)fields[5];
321  e.parent_insecure = (int)fields[6];
322  e.zwc_insecure = (int)fields[7];
323  if (zp_cc_vec_push(out, &e)) {
324  if (path_dup) {
325  zsfree(path_dup);
326  }
327  break;
328  }
329  }
330  fclose(fp);
331  return 0;
332 }
333 
334 /* Validate cache: any metadata difference triggers invalid (return 1). */
335 static int zp_cc_validate_cache(struct zp_cc_vec *cache) {
336  uid_t self = geteuid();
337  for (size_t i = 0; i < cache->size; i++) {
338  struct stat st;
339  if (stat(cache->items[i].path, &st) != 0) {
340  return 1;
341  }
342  if (cache->items[i].uid != st.st_uid || cache->items[i].gid != st.st_gid) {
343  return 1;
344  }
345  if (cache->items[i].mode != (st.st_mode & 07777)) {
346  return 1;
347  }
348  if (cache->items[i].ctime != st.st_ctime ||
349  cache->items[i].mtime != st.st_mtime) {
350  return 1;
351  }
352  int v = zp_cc_is_insecure(&st, self);
353  int p_insec = parent_insecure_path(cache->items[i].path, self);
354  if (!v && p_insec) {
355  v = 1;
356  }
357  if (v != cache->items[i].verdict) {
358  return 1;
359  }
360  }
361  return 0;
362 }
363 
364 /* Incremental update: refresh only changed entries and add new fpath dirs. */
365 // NOLINTBEGIN(bugprone-easily-swappable-parameters)
366 static int zp_cc_incremental_update(char *nam, char *cache_path,
367  struct zp_cc_vec *entries,
368  int *out_insecure, int *out_secure) {
369  uid_t self = geteuid();
370  /* Mark presence of existing entries */
371  for (size_t i = 0; i < entries->size;
372  i++) { /* reuse parent_insecure as temp mark? no; allocate bitmap
373  dynamically */
374  }
375  /* Gather fpath and add any missing directories */
376  char **arr = getaparam("fpath");
377  if (arr) {
378  for (char **fpath_it2 = arr; *fpath_it2; ++fpath_it2) {
379  if (!**fpath_it2) {
380  continue;
381  }
382  int found = 0;
383  for (size_t ent_idx = 0; ent_idx < entries->size; ent_idx++) {
384  if (strcmp(entries->items[ent_idx].path, *fpath_it2) == 0) {
385  found = 1;
386  break;
387  }
388  }
389  if (!found) {
390  size_t len = strlen(*fpath_it2) + 1;
391  char *dup = (char *)zalloc(len);
392  if (!dup) {
393  break;
394  }
395  memcpy(dup, *fpath_it2, len);
396  struct zp_cc_entry e;
397  e.path = dup;
398  e.verdict = 0;
399  e.mode = 0;
400  e.uid = 0;
401  e.gid = 0;
402  e.mtime = 0;
403  e.ctime = 0;
404  e.parent_insecure = 0;
405  e.zwc_insecure = 0; /* will fill */
406  if (zp_cc_vec_push(entries, &e)) {
407  zsfree(dup);
408  break;
409  }
410  }
411  }
412  }
413  int insecure = 0;
414  int secure = 0;
415  FILE *fp = fopen(cache_path, "w");
416  if (!fp) {
417  zwarnnam(nam, "%scompaudit-cache: cannot update %s: %s", zp_icon("❌ "),
418  cache_path, strerror(errno));
419  return 1;
420  }
421  fchmod(fileno(fp), 0600);
422  fprintf(fp, "version:3\n");
423  for (size_t i = 0; i < entries->size; i++) {
424  struct stat st;
425  if (stat(entries->items[i].path, &st) != 0) {
426  continue;
427  }
428  int verdict = zp_cc_is_insecure(&st, self);
429  int p_insec = parent_insecure_path(entries->items[i].path, self);
430  if (!verdict && p_insec) {
431  verdict = 1;
432  }
433  entries->items[i].verdict = verdict;
434  entries->items[i].mode = (st.st_mode & 07777);
435  entries->items[i].uid = st.st_uid;
436  entries->items[i].gid = st.st_gid;
437  entries->items[i].mtime = st.st_mtime;
438  entries->items[i].ctime = st.st_ctime;
439  entries->items[i].parent_insecure = p_insec;
440  if (verdict) {
441  insecure++;
442  } else {
443  secure++;
444  }
445  int zwc_insec = entries->items[i].zwc_insecure;
446  if (verdict && !zwc_insec) {
447  DIR *d = opendir(entries->items[i].path);
448  if (d) {
449  struct dirent *de;
450  while ((de = readdir(d))) {
451  if (de->d_name[0] == '.') {
452  continue;
453  }
454  size_t nlen = strlen(de->d_name);
455  if (nlen >= 4 && strcmp(de->d_name + nlen - 4, ".zwc") == 0) {
456  char full[PATH_MAX];
457  if (snprintf(full, sizeof(full), "%s/%s", entries->items[i].path,
458  de->d_name) < (int)sizeof(full)) {
459  struct stat stf;
460  if (stat(full, &stf) == 0 && (stf.st_mode & 022)) {
461  zwc_insec = 1;
462  break;
463  }
464  }
465  }
466  }
467  closedir(d);
468  }
469  }
470  entries->items[i].zwc_insecure = zwc_insec;
471  fprintf(fp, "%s\t%d\t%o\t%lu\t%lu\t%ld\t%ld\t%d\t%d\n",
472  entries->items[i].path, verdict, (unsigned)(st.st_mode & 07777),
473  (unsigned long)st.st_uid, (unsigned long)st.st_gid,
474  (long)st.st_mtime, (long)st.st_ctime, p_insec, zwc_insec);
475  }
476  fclose(fp);
477  if (out_insecure) {
478  *out_insecure = insecure;
479  }
480  if (out_secure) {
481  *out_secure = secure;
482  }
483  return 0;
484 }
485 // NOLINTEND(bugprone-easily-swappable-parameters)
486 
487 /* Rebuild cache: stat each target dir and write new file. */
488 static int parent_insecure_path(const char *path, uid_t self) {
489  char buf[PATH_MAX];
490  size_t len = strlen(path);
491  if (len >= sizeof(buf)) {
492  return 0;
493  }
494  memcpy(buf, path, len + 1);
495  while (1) {
496  char *slash = strrchr(buf, '/');
497  if (!slash || slash == buf) {
498  break;
499  }
500  *slash = '\0';
501  struct stat st;
502  if (stat(buf, &st) != 0) {
503  continue;
504  }
505  if (zp_cc_is_insecure(&st, self)) {
506  return 1;
507  }
508  }
509  return 0;
510 }
511 
512 // NOLINTBEGIN(bugprone-easily-swappable-parameters)
513 static int zp_cc_rebuild(char *nam, const char *file_path,
514  struct zp_cc_vec *targets, int *out_insecure,
515  int *out_secure) {
516  uid_t self = geteuid();
517  int insecure = 0;
518  int secure = 0;
519  FILE *fp = fopen(file_path, "w");
520  if (!fp) {
521  zwarnnam(nam, "%scompaudit-cache: cannot write %s: %s", zp_icon("❌ "),
522  file_path, strerror(errno));
523  return 1;
524  }
525  fchmod(fileno(fp), 0600);
526  fprintf(fp, "version:3\n");
527  for (size_t i = 0; i < targets->size; i++) {
528  struct stat st;
529  if (stat(targets->items[i].path, &st) != 0) {
530  continue; /* skip missing */
531  }
532  int verdict = zp_cc_is_insecure(&st, self);
533  int p_insec = parent_insecure_path(targets->items[i].path, self);
534  if (!verdict && p_insec) {
535  verdict = 1;
536  }
537  if (verdict) {
538  insecure++;
539  } else {
540  secure++;
541  }
542  int zwc_insec = 0;
543  if (verdict) {
544  DIR *d = opendir(targets->items[i].path);
545  if (d) {
546  struct dirent *de;
547  while ((de = readdir(d))) {
548  if (de->d_name[0] == '.') {
549  continue;
550  }
551  size_t nlen = strlen(de->d_name);
552  if (nlen >= 4 && strcmp(de->d_name + nlen - 4, ".zwc") == 0) {
553  char full[PATH_MAX];
554  if (snprintf(full, sizeof(full), "%s/%s", targets->items[i].path,
555  de->d_name) < (int)sizeof(full)) {
556  struct stat stf;
557  if (stat(full, &stf) == 0 && (stf.st_mode & 022)) {
558  zwc_insec = 1;
559  break;
560  }
561  }
562  }
563  }
564  closedir(d);
565  }
566  }
567  fprintf(fp, "%s\t%d\t%o\t%lu\t%lu\t%ld\t%ld\t%d\t%d\n",
568  targets->items[i].path, verdict, (unsigned)(st.st_mode & 07777),
569  (unsigned long)st.st_uid, (unsigned long)st.st_gid,
570  (long)st.st_mtime, (long)st.st_ctime, p_insec, zwc_insec);
571  }
572  fclose(fp);
573  if (out_insecure) {
574  *out_insecure = insecure;
575  }
576  if (out_secure) {
577  *out_secure = secure;
578  }
579  return 0;
580 }
581 // NOLINTEND(bugprone-easily-swappable-parameters)
582 
583 // NOLINTBEGIN(readability-function-cognitive-complexity,bugprone-easily-swappable-parameters)
584 int zp_compaudit_cache_core(char *nam, int rebuild, int show, int json) {
585  int ret = 0;
586  char *cache_path = zp_cc_cache_file_path(nam);
587  if (!cache_path) {
588  return 1;
589  }
590  /* Migration: if v3 missing but legacy v2 exists, force rebuild and remove v2
591  */
592  if (!rebuild) {
593  struct stat st_new;
594  if (stat(cache_path, &st_new) != 0) {
595  const char *needle = "_v3.zcache";
596  const char *rep = "_v2.zcache";
597  char *p = strstr(cache_path, needle);
598  if (p) {
599  size_t prefix = (size_t)(p - cache_path);
600  size_t v2len = prefix + strlen(rep) + 1;
601  char *v2path = (char *)zalloc(v2len);
602  if (v2path) {
603  memcpy(v2path, cache_path, prefix);
604  strcpy(v2path + prefix, rep);
605  struct stat st_old;
606  if (stat(v2path, &st_old) == 0) {
607  rebuild = 1;
608  unlink(v2path); /* best-effort removal */
609  }
610  zsfree(v2path);
611  }
612  }
613  }
614  }
615 
616  struct zp_cc_vec cache_entries;
617  zp_cc_vec_init(&cache_entries);
618  int have_cache = 0;
619  int insecure = 0;
620  int secure = 0; /* may be filled by incremental update */
621  struct stat cst;
622  if (!rebuild && stat(cache_path, &cst) == 0) {
623  if ((cst.st_mode & S_IWOTH) || (cst.st_mode & S_IWGRP)) {
624  rebuild = 1;
625  }
626  }
627  if (!rebuild) {
628  if (zp_cc_load_cache(cache_path, &cache_entries) == 0) {
629  have_cache = 1;
630  if (zp_cc_validate_cache(&cache_entries) != 0) {
631  /* Perform incremental update instead of discarding entirely */
632  if (zp_cc_incremental_update(nam, cache_path, &cache_entries, &insecure,
633  &secure) == 0) {
634  /* reload fresh for show/json uniformity */
635  zp_cc_vec_free(&cache_entries);
636  zp_cc_vec_init(&cache_entries);
637  zp_cc_load_cache(cache_path, &cache_entries);
638  have_cache = 1;
639  } else {
640  zp_cc_vec_free(&cache_entries);
641  have_cache = 0; /* fallback to full rebuild */
642  }
643  }
644  }
645  }
646  if (insecure || secure) { /* already populated by incremental update path */
647  }
648  if (!have_cache) {
649  /* Need targets to rebuild */
650  struct zp_cc_vec targets;
651  zp_cc_vec_init(&targets);
652  zp_cc_collect_dirs(&targets);
653  if (zp_cc_rebuild(nam, cache_path, &targets, &insecure, &secure) != 0) {
654  zp_cc_vec_free(&targets);
655  zsfree(cache_path);
656  return 1;
657  }
658  zp_cc_vec_free(&targets);
659  /* load freshly written for uniformity if show requested */
660  if (show || json) {
661  zp_cc_load_cache(cache_path, &cache_entries);
662  }
663  } else {
664  /* derive counts from existing entries */
665  for (size_t i = 0; i < cache_entries.size; i++) {
666  if (cache_entries.items[i].verdict) {
667  insecure++;
668  } else {
669  secure++;
670  }
671  }
672  }
673  if (json) {
674  fprintf(stdout, "{\"insecure\":%d,\"secure\":%d,\"dirs\":[", insecure,
675  secure);
676  uid_t self = geteuid();
677  for (size_t i = 0; i < cache_entries.size; i++) {
678  struct zp_cc_entry *e = &cache_entries.items[i];
679  int reason_dir = 0;
680  int reason_ancestor = 0;
681  int reason_zwc = 0;
682  if (e->verdict) {
683  /* directory perms */
684  struct stat st_dir;
685  if (stat(e->path, &st_dir) == 0) {
686  if (zp_cc_is_insecure(&st_dir, self)) {
687  reason_dir = 1;
688  }
689  }
690  if (e->parent_insecure) {
691  reason_ancestor = 1;
692  }
693  if (e->zwc_insecure) {
694  reason_zwc = 1;
695  }
696  }
697  fprintf(stdout, "%s{\"path\":\"", i ? "," : "");
698  const char *p = e->path;
699  for (; *p; p++) {
700  if (*p == '\\' || *p == '\"') {
701  fputc('\\', stdout);
702  }
703  fputc(*p, stdout);
704  }
705  fprintf(stdout, "\",\"verdict\":%d,\"parent_insecure\":%d,\"reasons\":[",
706  e->verdict, e->parent_insecure);
707  int first = 1;
708  if (reason_dir) {
709  fprintf(stdout, "\"dir_perms\"");
710  first = 0;
711  }
712  if (reason_ancestor) {
713  fprintf(stdout, "%s\"ancestor_perms\"", first ? "" : " ,");
714  first = 0;
715  }
716  if (reason_zwc) {
717  fprintf(stdout, "%s\"zwc_perms\"", first ? "" : " ,");
718  }
719  fprintf(stdout, "]}");
720  }
721  fprintf(stdout, "]}\n");
722  fflush(stdout);
723  } else if (show) {
724  fprintf(stdout, "%scompaudit-cache: insecure %d secure %d\n",
725  zp_icon("🔐 "), insecure, secure);
726  if (cache_entries.size) {
727  for (size_t i = 0; i < cache_entries.size; i++) {
728  if (cache_entries.items[i].verdict) {
729  fprintf(stdout, " ! %s%s\n", cache_entries.items[i].path,
730  cache_entries.items[i].parent_insecure ? " (ancestor)" : "");
731  }
732  }
733  }
734  fflush(stdout);
735  }
736  zp_cc_vec_free(&cache_entries);
737  zsfree(cache_path);
738  return ret;
739 }
740 // NOLINTEND(readability-function-cognitive-complexity,bugprone-easily-swappable-parameters)
741 
742 // NOLINTEND(misc-include-cleaner)
static void zp_cc_collect_dirs(struct zp_cc_vec *out)
static void zp_cc_vec_init(struct zp_cc_vec *v)
#define ZP_COMPAUDIT_CACHE_FILE
static void zp_cc_discover_exe_owner(void)
static int zp_cc_validate_cache(struct zp_cc_vec *cache)
static char * zp_cc_cache_file_path(char *nam)
int zp_compaudit_cache_core(char *nam, int rebuild, int show, int json)
static char * zp_cc_ensure_dir(char *nam)
static uid_t zp_cached_exe_owner
static int zp_cc_incremental_update(char *nam, char *cache_path, struct zp_cc_vec *entries, int *out_insecure, int *out_secure)
static char * zp_cc_base_cache_dir(void)
static int zp_cc_load_cache(const char *path, struct zp_cc_vec *out)
static int zp_cc_vec_push(struct zp_cc_vec *v, struct zp_cc_entry *e)
static void zp_cc_vec_free(struct zp_cc_vec *v)
static int zp_cc_rebuild(char *nam, const char *file_path, struct zp_cc_vec *targets, int *out_insecure, int *out_secure)
static int zp_cc_owner_allowed(uid_t uid, uid_t self)
static int zp_cc_is_insecure(struct stat *st, uid_t self)
static int parent_insecure_path(const char *path, uid_t self)
#define PATH_MAX
#define ZP_COMPAUDIT_CACHE_SUBDIR
const char * zp_icon(const char *s)
Return icon string if enabled, empty string otherwise.
Definition: emoji.c:58
gid_t gid
time_t ctime
char * path
int verdict
mode_t mode
time_t mtime
uid_t uid
int zwc_insecure
int parent_insecure
struct zp_cc_entry * items
Module declaration header (mdh) for zpmod.
Prototype stub for zpmod when building out-of-tree.
Interface for cached compaudit security verdicts.
Optional terminal/locale detection for emoji support in messages.
void * zalloc(size_t size)
void zsfree(char *ptr)
void zfree(void *ptr, size_t size)
char ** getaparam(const char *name)
void * zrealloc(void *ptr, size_t size)
char * getsparam(const char *name)
void zwarnnam(const char *, const char *,...)
char ** path
char * ztrdup(const char *)