diff options
-rw-r--r-- | doc/apk-audit.8.scd | 11 | ||||
-rw-r--r-- | src/apk_database.h | 6 | ||||
-rw-r--r-- | src/app_audit.c | 126 | ||||
-rw-r--r-- | 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 90fb292..d63da2d 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 ecf3e22..c1d8b10 100644 --- a/src/app_audit.c +++ b/src/app_audit.c @@ -25,11 +25,12 @@ enum { MODE_BACKUP = 0, - MODE_SYSTEM + MODE_SYSTEM, + MODE_FULL, }; struct audit_ctx { - unsigned mode : 1; + unsigned mode : 2; unsigned recursive : 1; unsigned check_permissions : 1; unsigned packages_only : 1; @@ -38,6 +39,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") \ @@ -54,6 +56,24 @@ static int option_parse_applet(void *ctx, struct apk_db_options *dbopts, int opt case OPT_AUDIT_backup: actx->mode = MODE_BACKUP; break; + case OPT_AUDIT_full: + actx->mode = MODE_FULL; + if (APK_BLOB_IS_NULL(dbopts->protected_paths)) + dbopts->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; @@ -103,8 +123,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) | @@ -172,6 +190,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; @@ -194,17 +229,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); @@ -226,47 +267,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); } @@ -306,11 +350,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; } @@ -344,7 +388,7 @@ static int audit_main(void *ctx, struct apk_database *db, struct apk_string_arra 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 282e3a0..140a9ef 100644 --- a/src/database.c +++ b/src/database.c @@ -341,7 +341,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); @@ -372,7 +372,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; @@ -1287,7 +1287,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; @@ -2735,7 +2735,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) || (apk_flags & APK_PURGE) || (file->csum.type != APK_CHECKSUM_NONE && apk_fileinfo_get(db->root_fd, name, APK_FI_NOFOLLOW | file->csum.type, &fi, &db->atoms) == 0 && @@ -2799,7 +2799,7 @@ static void apk_db_migrate_files(struct apk_database *db, /* File was from overlay, delete the * packages version */ unlinkat(db->root_fd, tmpname, 0); - } else if ((diri->dir->protect_mode != APK_PROTECT_NONE) && + } else if (!apk_protect_mode_none(diri->dir->protect_mode) && (r == 0) && (ofile == NULL || ofile->csum.type == APK_CHECKSUM_NONE || |