From 800f4dd1fd1b0bd8eddd4d574058f75f6877f833 Mon Sep 17 00:00:00 2001 From: Timo Teräs Date: Mon, 10 Apr 2023 20:22:04 +0300 Subject: audit: add a new --full mode --- doc/apk-audit.8.scd | 11 ++++- src/apk_database.h | 6 +++ src/app_audit.c | 126 +++++++++++++++++++++++++++++++++++----------------- src/database.c | 10 ++--- 4 files changed, 105 insertions(+), 48 deletions(-) diff --git a/doc/apk-audit.8.scd b/doc/apk-audit.8.scd index 2294f8a..19a7ba9 100644 --- a/doc/apk-audit.8.scd +++ b/doc/apk-audit.8.scd @@ -15,8 +15,8 @@ the package database. The audit can be done against configuration files only (--backup) to generate list of files needed to be stored in the overlay in run-from-tmps configuration. -Alternatively, it can audit all installed files (--system) to e.g. detect -unauthorized modifications of system files. +Alternatively, it can audit all installed files (--system or --full) to +e.g. detect unauthorized modifications of system files. By default, the output format is one file per line, for each modified file. A character is printed indicating the change detected, followed by a space, @@ -49,6 +49,12 @@ then the affected path. The changes detected are: Check file permissions too. Namely, the uid, gid and file mode will be checked in addition to the file content. +*--full* + Audit all system files. Same as *--system*, but in addition reports + all added directories and files. A built-in default override for + protected paths is used, unless a *--protected-paths* is explicitly + specified. + *--packages* Print only the packages with changed files. Instead of the full output each modification, the set of packages with at least one modified file @@ -65,6 +71,7 @@ then the affected path. The changes detected are: Audit all system files. All files provided by packages are verified for integrity with the exception of configuration files (listed in protected_paths.d). This is useful detecting unauthorized file changes. + New files or directories are not reported. *-r, --recursive* Descend into directories and audit them as well. diff --git a/src/apk_database.h b/src/apk_database.h index b1649d1..d0a33a7 100644 --- a/src/apk_database.h +++ b/src/apk_database.h @@ -45,11 +45,17 @@ struct apk_db_file { enum apk_protect_mode { APK_PROTECT_NONE = 0, + APK_PROTECT_IGNORE, APK_PROTECT_CHANGED, APK_PROTECT_SYMLINKS_ONLY, APK_PROTECT_ALL, }; +static inline int apk_protect_mode_none(enum apk_protect_mode mode) +{ + return mode == APK_PROTECT_NONE || mode == APK_PROTECT_IGNORE; +} + struct apk_protected_path { char *relative_pattern; unsigned protect_mode : 3; diff --git a/src/app_audit.c b/src/app_audit.c index f9f6690..fb5ba42 100644 --- a/src/app_audit.c +++ b/src/app_audit.c @@ -25,12 +25,13 @@ enum { MODE_BACKUP = 0, - MODE_SYSTEM + MODE_SYSTEM, + MODE_FULL, }; struct audit_ctx { int verbosity; - unsigned mode : 1; + unsigned mode : 2; unsigned recursive : 1; unsigned check_permissions : 1; unsigned packages_only : 1; @@ -39,6 +40,7 @@ struct audit_ctx { #define AUDIT_OPTIONS(OPT) \ OPT(OPT_AUDIT_backup, "backup") \ OPT(OPT_AUDIT_check_permissions, "check-permissions") \ + OPT(OPT_AUDIT_full, "full") \ OPT(OPT_AUDIT_packages, "packages") \ OPT(OPT_AUDIT_protected_paths, APK_OPT_ARG "protected-paths") \ OPT(OPT_AUDIT_recursive, APK_OPT_SH("r") "recursive") \ @@ -56,6 +58,24 @@ static int option_parse_applet(void *applet_ctx, struct apk_ctx *ac, int opt, co case OPT_AUDIT_backup: actx->mode = MODE_BACKUP; break; + case OPT_AUDIT_full: + actx->mode = MODE_FULL; + if (APK_BLOB_IS_NULL(ac->protected_paths)) + ac->protected_paths = APK_BLOB_STR( + "+etc\n" + "@etc/init.d\n" + "-dev\n" + "-home\n" + "-lib/apk\n" + "-lib/rc/cache\n" + "-proc\n" + "-root\n" + "-run\n" + "-sys\n" + "-tmp\n" + "-var\n" + ); + break; case OPT_AUDIT_system: actx->mode = MODE_SYSTEM; break; @@ -105,8 +125,6 @@ static int audit_file(struct audit_ctx *actx, if (dbf == NULL) return 'A'; - dbf->audited = 1; - if (apk_fileinfo_get(dirfd, name, APK_FI_NOFOLLOW | APK_FI_XATTR_CSUM(dbf->acl->xattr_csum.type ?: APK_CHECKSUM_DEFAULT) | @@ -175,6 +193,23 @@ static void report_audit(struct audit_ctx *actx, printf("%c " BLOB_FMT "\n", reason, BLOB_PRINTF(bfull)); } +static int determine_file_protect_mode(struct apk_db_dir *dir, const char *name) +{ + struct apk_protected_path *ppath; + int protect_mode = dir->protect_mode; + + /* inherit file's protection mask */ + foreach_array_item(ppath, dir->protected_paths) { + char *slash = strchr(ppath->relative_pattern, '/'); + if (slash == NULL) { + if (fnmatch(ppath->relative_pattern, name, FNM_PATHNAME) != 0) + continue; + protect_mode = ppath->protect_mode; + } + } + return protect_mode; +} + static int audit_directory_tree_item(void *ctx, int dirfd, const char *name) { struct audit_tree_ctx *atctx = (struct audit_tree_ctx *) ctx; @@ -197,17 +232,23 @@ static int audit_directory_tree_item(void *ctx, int dirfd, const char *name) if (S_ISDIR(fi.mode)) { int recurse = TRUE; - if (actx->mode == MODE_BACKUP) { + switch (actx->mode) { + case MODE_BACKUP: child = apk_db_dir_get(db, bfull); if (!child->has_protected_children) recurse = FALSE; - if (child->protect_mode == APK_PROTECT_NONE) + if (apk_protect_mode_none(child->protect_mode)) goto recurse_check; - } else { + break; + case MODE_SYSTEM: child = apk_db_dir_query(db, bfull); - if (child == NULL) - goto done; + if (child == NULL) goto done; child = apk_db_dir_ref(child); + break; + case MODE_FULL: + child = apk_db_dir_get(db, bfull); + if (child->protect_mode == APK_PROTECT_NONE) break; + goto recurse_check; } reason = audit_directory(actx, db, child, &fi); @@ -229,47 +270,50 @@ recurse_check: atctx->pathlen--; } else { struct apk_db_file *dbf; - struct apk_protected_path *ppath; - int protect_mode = dir->protect_mode; - - /* inherit file's protection mask */ - foreach_array_item(ppath, dir->protected_paths) { - char *slash = strchr(ppath->relative_pattern, '/'); - if (slash == NULL) { - if (fnmatch(ppath->relative_pattern, name, FNM_PATHNAME) != 0) - continue; - protect_mode = ppath->protect_mode; - } - } + int protect_mode = determine_file_protect_mode(dir, name); - if (actx->mode == MODE_BACKUP) { + dbf = apk_db_file_query(db, bdir, bent); + if (dbf) dbf->audited = 1; + + switch (actx->mode) { + case MODE_FULL: + switch (protect_mode) { + case APK_PROTECT_NONE: + break; + case APK_PROTECT_SYMLINKS_ONLY: + if (S_ISLNK(fi.mode)) goto done; + break; + case APK_PROTECT_IGNORE: + case APK_PROTECT_ALL: + case APK_PROTECT_CHANGED: + goto done; + } + break; + case MODE_BACKUP: switch (protect_mode) { case APK_PROTECT_NONE: + case APK_PROTECT_IGNORE: goto done; case APK_PROTECT_CHANGED: break; case APK_PROTECT_SYMLINKS_ONLY: - if (!S_ISLNK(fi.mode)) - goto done; + if (!S_ISLNK(fi.mode)) goto done; break; case APK_PROTECT_ALL: reason = 'A'; break; } + if ((!dbf || reason == 'A') && + apk_blob_ends_with(bent, APK_BLOB_STR(".apk-new"))) + goto done; + break; + case MODE_SYSTEM: + if (!dbf || !apk_protect_mode_none(protect_mode)) goto done; + break; } - dbf = apk_db_file_query(db, bdir, bent); - if (reason == 0) - reason = audit_file(actx, db, dbf, dirfd, name); - if (reason < 0) - goto done; - if (actx->mode == MODE_SYSTEM && - (reason == 'A' || protect_mode != APK_PROTECT_NONE)) - goto done; - if (actx->mode == MODE_BACKUP && - reason == 'A' && - apk_blob_ends_with(bent, APK_BLOB_STR(".apk-new"))) - goto done; + if (reason == 0) reason = audit_file(actx, db, dbf, dirfd, name); + if (reason < 0) goto done; report_audit(actx, reason, bfull, dbf ? dbf->diri->pkg : NULL); } @@ -309,11 +353,11 @@ static int audit_missing_files(apk_hash_item item, void *pctx) if (file->audited) return 0; dir = file->diri->dir; - if (dir->mode & S_SEENFLAG) { - len = snprintf(path, sizeof(path), DIR_FILE_FMT, DIR_FILE_PRINTF(dir, file)); - report_audit(actx, 'X', APK_BLOB_PTR_LEN(path, len), file->diri->pkg); - } + if (!(dir->mode & S_SEENFLAG)) return 0; + if (determine_file_protect_mode(dir, file->name) == APK_PROTECT_IGNORE) return 0; + len = snprintf(path, sizeof(path), DIR_FILE_FMT, DIR_FILE_PRINTF(dir, file)); + report_audit(actx, 'X', APK_BLOB_PTR_LEN(path, len), file->diri->pkg); return 0; } @@ -350,7 +394,7 @@ static int audit_main(void *ctx, struct apk_ctx *ac, struct apk_string_array *ar r |= audit_directory_tree(&atctx, openat(db->root_fd, arg, O_RDONLY|O_CLOEXEC)); } } - if (actx->mode == MODE_SYSTEM) + if (actx->mode == MODE_SYSTEM || actx->mode == MODE_FULL) apk_hash_foreach(&db->installed.files, audit_missing_files, ctx); return r; diff --git a/src/database.c b/src/database.c index 50d6621..6eca81e 100644 --- a/src/database.c +++ b/src/database.c @@ -362,7 +362,7 @@ struct apk_db_dir *apk_db_dir_get(struct apk_database *db, apk_blob_t name) } else if (apk_blob_rsplit(name, '/', &bparent, NULL)) { dir->parent = apk_db_dir_get(db, bparent); dir->protect_mode = dir->parent->protect_mode; - dir->has_protected_children = (dir->protect_mode != APK_PROTECT_NONE); + dir->has_protected_children = !apk_protect_mode_none(dir->protect_mode); ppaths = dir->parent->protected_paths; } else { dir->parent = apk_db_dir_get(db, APK_BLOB_NULL); @@ -393,7 +393,7 @@ struct apk_db_dir *apk_db_dir_get(struct apk_database *db, apk_blob_t name) dir->protect_mode = ppath->protect_mode; } - dir->has_protected_children |= (ppath->protect_mode != APK_PROTECT_NONE); + dir->has_protected_children |= !apk_protect_mode_none(ppath->protect_mode); } return dir; @@ -1318,7 +1318,7 @@ static int add_protected_path(void *ctx, apk_blob_t blob) case '#': return 0; case '-': - protect_mode = APK_PROTECT_NONE; + protect_mode = APK_PROTECT_IGNORE; break; case '+': protect_mode = APK_PROTECT_CHANGED; @@ -2857,7 +2857,7 @@ static void apk_db_purge_pkg(struct apk_database *db, }; hash = apk_blob_hash_seed(key.filename, diri->dir->hash); if (!is_installed || - (diri->dir->protect_mode == APK_PROTECT_NONE) || + apk_protect_mode_none(diri->dir->protect_mode) || (db->ctx->flags & APK_PURGE) || apk_db_audit_file(&d, key.filename, file) == 0) apk_fsdir_file_control(&d, key.filename, ctrl); @@ -2920,7 +2920,7 @@ static uint8_t apk_db_migrate_files_for_priority(struct apk_database *db, if (ofile && ofile->diri->pkg->name == NULL) { // File was from overlay, delete the package's version ctrl = APK_FS_CTRL_CANCEL; - } else if (diri->dir->protect_mode != APK_PROTECT_NONE && + } else if (!apk_protect_mode_none(diri->dir->protect_mode) && apk_db_audit_file(&d, key.filename, ofile) != 0) { // Protected directory, and a file without db entry // or with local modifications. Keep the filesystem file. -- cgit v1.2.3-60-g2f50