2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 10:07:12 +00:00

Merge Port aa-status from python to C

This allows aa-status to be used without a python runtime to support things like https://bugs.launchpad.net/bugs/1865519

Fixes: https://bugs.launchpad.net/bugs/1865519
PR: https://gitlab.com/apparmor/apparmor/-/merge_requests/473
Acked-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
Alex Murray 2020-04-24 05:43:47 +00:00 committed by John Johansen
parent e9c2a6087d
commit 8f9046b1b1
5 changed files with 633 additions and 276 deletions

View File

@ -19,8 +19,9 @@ include $(COMMONDIR)/Make.rules
DESTDIR=/ DESTDIR=/
BINDIR=${DESTDIR}/usr/bin BINDIR=${DESTDIR}/usr/bin
SBINDIR=${DESTDIR}/usr/sbin
LOCALEDIR=/usr/share/locale LOCALEDIR=/usr/share/locale
MANPAGES=aa-enabled.1 aa-exec.1 MANPAGES=aa-enabled.1 aa-exec.1 aa-status.8
WARNINGS = -Wall WARNINGS = -Wall
EXTRA_WARNINGS = -Wsign-compare -Wmissing-field-initializers -Wformat-security -Wunused-parameter EXTRA_WARNINGS = -Wsign-compare -Wmissing-field-initializers -Wformat-security -Wunused-parameter
@ -50,7 +51,8 @@ EXTRA_CFLAGS+=-DPACKAGE=\"${NAME}\" -DLOCALEDIR=\"${LOCALEDIR}\"
SRCS = aa_enabled.c SRCS = aa_enabled.c
HDRS = HDRS =
TOOLS = aa-enabled aa-exec BINTOOLS = aa-enabled aa-exec
SBINTOOLS = aa-status
AALIB = -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread AALIB = -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread
@ -93,7 +95,7 @@ po/%.pot: %.c
# targets arranged this way so that people who don't want full docs can # targets arranged this way so that people who don't want full docs can
# pick specific targets they want. # pick specific targets they want.
arch: $(TOOLS) arch: $(BINTOOLS) $(SBINTOOLS)
manpages: $(MANPAGES) manpages: $(MANPAGES)
@ -106,7 +108,7 @@ all: arch indep
.PHONY: coverage .PHONY: coverage
coverage: coverage:
$(MAKE) clean $(TOOLS) COVERAGE=1 $(MAKE) clean $(BINTOOLS) $(SBINTOOLS) COVERAGE=1
ifndef USE_SYSTEM ifndef USE_SYSTEM
$(LIBAPPARMOR_A): $(LIBAPPARMOR_A):
@ -124,12 +126,15 @@ aa-enabled: aa_enabled.c $(LIBAPPARMOR_A)
aa-exec: aa_exec.c $(LIBAPPARMOR_A) aa-exec: aa_exec.c $(LIBAPPARMOR_A)
$(CC) $(LDFLAGS) $(EXTRA_CFLAGS) -o $@ $< $(LIBS) $(AALIB) $(CC) $(LDFLAGS) $(EXTRA_CFLAGS) -o $@ $< $(LIBS) $(AALIB)
aa-status: aa_status.c $(LIBAPPARMOR_A)
$(CC) $(LDFLAGS) $(EXTRA_CFLAGS) -o $@ $< $(LIBS) $(AALIB)
.SILENT: check .SILENT: check
.PHONY: check .PHONY: check
check: check_pod_files tests check: check_pod_files tests
.SILENT: tests .SILENT: tests
tests: $(TOOLS) $(TESTS) tests: $(BINTOOLS) $(SBINTOOLS) $(TESTS)
echo "no tests atm" echo "no tests atm"
.PHONY: install .PHONY: install
@ -138,7 +143,11 @@ install: install-indep install-arch
.PHONY: install-arch .PHONY: install-arch
install-arch: arch install-arch: arch
install -m 755 -d ${BINDIR} install -m 755 -d ${BINDIR}
install -m 755 ${TOOLS} ${BINDIR} install -m 755 ${BINTOOLS} ${BINDIR}
install -m 755 -d ${SBINDIR}
ln -sf aa-status ${SBINDIR}/apparmor_status
install -m 755 ${SBINTOOLS} ${SBINDIR}
ln -sf aa-status.8 ${DESTDIR}/${MANDIR}/man8/apparmor_status.8
.PHONY: install-indep .PHONY: install-indep
install-indep: indep install-indep: indep

View File

@ -119,6 +119,10 @@ if the apparmor control files aren't available under /sys/kernel/security/.
if the user running the script doesn't have enough privileges to read if the user running the script doesn't have enough privileges to read
the apparmor control files. the apparmor control files.
=item B<42>
if an internal error occurred.
=back =back
=head1 BUGS =head1 BUGS

613
binutils/aa_status.c Normal file
View File

@ -0,0 +1,613 @@
/*
* Copyright (C) 2020 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*/
#define _GNU_SOURCE /* for asprintf() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <regex.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/apparmor.h>
#include <sys/apparmor_private.h>
#define autofree __attribute((cleanup(_aa_autofree)))
#define autofclose __attribute((cleanup(_aa_autofclose)))
#define AA_EXIT_ENABLED 0
#define AA_EXIT_DISABLED 1
#define AA_EXIT_NO_POLICY 2
#define AA_EXIT_NO_CONTROL 3
#define AA_EXIT_NO_PERM 4
#define AA_EXIT_INTERNAL_ERROR 42
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
struct profile {
char *name;
char *status;
};
static void free_profiles(struct profile *profiles, size_t n) {
while (n > 0) {
n--;
free(profiles[n].name);
free(profiles[n].status);
}
free(profiles);
}
struct process {
char *pid;
char *profile;
char *exe;
char *mode;
};
static void free_processes(struct process *processes, size_t n) {
while (n > 0) {
n--;
free(processes[n].pid);
free(processes[n].profile);
free(processes[n].exe);
free(processes[n].mode);
}
free(processes);
}
static int verbose = 0;
#define dprintf(...) \
do { \
if (verbose) \
printf(__VA_ARGS__); \
} while (0)
#define dfprintf(...) \
do { \
if (verbose) \
fprintf(__VA_ARGS__); \
} while (0)
static int get_profiles(struct profile **profiles, size_t *n) {
autofree char *apparmorfs = NULL;
autofree char *apparmor_profiles = NULL;
struct stat st;
autofclose FILE *fp = NULL;
regex_t regex;
autofree char *line = NULL;
size_t len = 0;
int ret;
*profiles = NULL;
*n = 0;
ret = stat("/sys/module/apparmor", &st);
if (ret != 0) {
dfprintf(stderr, "apparmor not present.\n");
ret = AA_EXIT_DISABLED;
goto exit;
}
dprintf("apparmor module is loaded.\n");
ret = aa_find_mountpoint(&apparmorfs);
if (ret == -1) {
dfprintf(stderr, "apparmor filesystem is not mounted.\n");
ret = AA_EXIT_NO_CONTROL;
goto exit;
}
apparmor_profiles = malloc(strlen(apparmorfs) + 10); // /profiles\0
if (apparmor_profiles == NULL) {
ret = AA_EXIT_INTERNAL_ERROR;
goto exit;
}
sprintf(apparmor_profiles, "%s/profiles", apparmorfs);
fp = fopen(apparmor_profiles, "r");
if (fp == NULL) {
if (errno == EACCES) {
dfprintf(stderr, "You do not have enough privilege to read the profile set.\n");
} else {
dfprintf(stderr, "Could not open %s: %s", apparmor_profiles, strerror(errno));
}
ret = AA_EXIT_NO_PERM;
goto exit;
}
ret = regcomp(&regex, "^(.+)\\s+\\((.+)\\).*", REG_EXTENDED | REG_NEWLINE);
if (ret != 0) {
ret = AA_EXIT_INTERNAL_ERROR;
goto exit;
}
while (getline(&line, &len, fp) != -1) {
regmatch_t match[3];
ret = regexec(&regex, line, 3, match, 0);
if (ret == 0) {
size_t i;
struct profile *_profiles;
autofree char *name = strndup(line + match[1].rm_so,
match[1].rm_eo - match[1].rm_so);
autofree char *status = strndup(line + match[2].rm_so,
match[2].rm_eo - match[2].rm_so);
// give up if out of memory
if (name == NULL || status == NULL) {
free_profiles(*profiles, *n);
*profiles = NULL;
*n = 0;
ret = AA_EXIT_INTERNAL_ERROR;
break;
}
_profiles = realloc(*profiles, (*n + 1) * sizeof(**profiles));
if (_profiles == NULL) {
free_profiles(*profiles, *n);
*profiles = NULL;
*n = 0;
ret = AA_EXIT_INTERNAL_ERROR;
break;
}
// steal name and status
_profiles[*n].name = name;
_profiles[*n].status = status;
name = NULL;
status = NULL;
*n = *n + 1;
*profiles = _profiles;
}
}
regfree(&regex);
exit:
return ret == 0 ? (*n > 0 ? AA_EXIT_ENABLED : AA_EXIT_NO_POLICY) : ret;
}
static int compare_profiles(const void *a, const void *b) {
return strcmp(((struct profile *)a)->name,
((struct profile *)b)->name);
}
static int filter_profiles(struct profile *profiles,
size_t n,
const char *filter,
struct profile **filtered,
size_t *nfiltered)
{
int ret = 0;
size_t i;
*filtered = NULL;
*nfiltered = 0;
for (i = 0; i < n; i++) {
if (filter == NULL || strcmp(profiles[i].status, filter) == 0) {
struct profile *_filtered = realloc(*filtered, (*nfiltered + 1) * sizeof(**filtered));
if (_filtered == NULL) {
free_profiles(*filtered, *nfiltered);
*filtered = NULL;
*nfiltered = 0;
ret = AA_EXIT_INTERNAL_ERROR;
break;
}
_filtered[*nfiltered].name = strdup(profiles[i].name);
_filtered[*nfiltered].status = strdup(profiles[i].status);
*filtered = _filtered;
*nfiltered = *nfiltered + 1;
}
}
if (*nfiltered != 0) {
qsort(*filtered, *nfiltered, sizeof(*profiles), compare_profiles);
}
return ret;
}
static int get_processes(struct profile *profiles,
size_t n,
struct process **processes,
size_t *nprocesses)
{
DIR *dir = NULL;
struct dirent *entry = NULL;
regex_t regex;
int ret;
*processes = NULL;
*nprocesses = 0;
ret = regcomp(&regex, "^(.*)\\s+\\((.*)\\)\n$", REG_EXTENDED | REG_NEWLINE);
if (ret != 0) {
ret = AA_EXIT_INTERNAL_ERROR;
goto exit;
}
dir = opendir("/proc");
if (dir == NULL) {
ret = AA_EXIT_INTERNAL_ERROR;
goto free_regex;
}
while ((entry = readdir(dir)) != NULL) {
int i;
int ispid = 1;
autofree char *current = NULL;
autofree char *exe = NULL;
autofree char *real_exe = NULL;
autofclose FILE *fp = NULL;
autofree char *line = NULL;
size_t len = 0;
// ignore non-pid entries
for (i = 0; ispid && i < strlen(entry->d_name); i++) {
ispid = (isdigit(entry->d_name[i]) ? 1 : 0);
}
if (!ispid) {
continue;
}
if (asprintf(&current, "/proc/%s/attr/current", entry->d_name) == -1 ||
asprintf(&exe, "/proc/%s/exe", entry->d_name) == -1) {
fprintf(stderr, "ERROR: Failed to allocate memory\n");
ret = AA_EXIT_INTERNAL_ERROR;
goto free_regex;
}
// get executable - readpath can allocate for us but seems
// to fail in some cases with errno 2 - no such file or
// directory - whereas readlink() can succeed in these
// cases - and readpath() seems to have the same behaviour
// as in python with better canonicalized results so try it
// first and fallack to readlink if it fails
// coverity[toctou]
real_exe = realpath(exe, NULL);
if (real_exe == NULL) {
int res;
// ensure enough space for NUL terminator
real_exe = calloc(PATH_MAX + 1, sizeof(char));
if (real_exe == NULL) {
fprintf(stderr, "ERROR: Failed to allocate memory\n");
ret = AA_EXIT_INTERNAL_ERROR;
goto free_regex;
}
res = readlink(exe, real_exe, PATH_MAX);
if (res == -1) {
continue;
}
real_exe[res] = '\0';
}
// see if has a label
fp = fopen(current, "r");
if (fp == NULL) {
continue;
}
while (getline(&line, &len, fp) != -1) {
autofree char *profile = NULL;
autofree char *mode = NULL;
regmatch_t match[3];
int res;
res = regexec(&regex, line, 3, match, 0);
if (res == 0) {
profile = strndup(line + match[1].rm_so,
match[1].rm_eo - match[1].rm_so);
mode = strndup(line + match[2].rm_so,
match[2].rm_eo - match[2].rm_so);
} else {
// is unconfined so keep only if this has a
// matching profile
for (i = 0; i < n; i++) {
if (strcmp(profiles[i].name, real_exe) == 0) {
profile = strdup(real_exe);
mode = strdup("unconfined");
break;
}
}
}
if (profile != NULL && mode != NULL) {
struct process *_processes = realloc(*processes,
(*nprocesses + 1) * sizeof(**processes));
if (_processes == NULL) {
free_processes(*processes, *nprocesses);
*processes = NULL;
*nprocesses = 0;
ret = AA_EXIT_INTERNAL_ERROR;
goto free_regex;
}
_processes[*nprocesses].pid = strdup(entry->d_name);
_processes[*nprocesses].profile = profile;
_processes[*nprocesses].exe = strdup(real_exe);
_processes[*nprocesses].mode = mode;
*processes = _processes;
*nprocesses = *nprocesses + 1;
profile = NULL;
mode = NULL;
ret = AA_EXIT_ENABLED;
}
}
}
free_regex:
regfree(&regex);
exit:
if (dir != NULL) {
closedir(dir);
}
return ret;
}
static int filter_processes(struct process *processes,
size_t n,
const char *filter,
struct process **filtered,
size_t *nfiltered)
{
size_t i;
int ret = 0;
*filtered = NULL;
*nfiltered = 0;
for (i = 0; i < n; i++) {
if (filter == NULL || strcmp(processes[i].mode, filter) == 0) {
struct process *_filtered = realloc(*filtered, (*nfiltered + 1) * sizeof(**filtered));
if (_filtered == NULL) {
free_processes(*filtered, *nfiltered);
*filtered = NULL;
*nfiltered = 0;
ret = AA_EXIT_INTERNAL_ERROR;
break;
}
_filtered[*nfiltered].pid = strdup(processes[i].pid);
_filtered[*nfiltered].profile = strdup(processes[i].profile);
_filtered[*nfiltered].exe = strdup(processes[i].exe);
_filtered[*nfiltered].mode = strdup(processes[i].mode);
*filtered = _filtered;
*nfiltered = *nfiltered + 1;
}
}
return ret;
}
/**
* Returns error code if AppArmor is not enabled
*/
static int simple_filtered_count(const char *filter) {
size_t n;
struct profile *profiles;
int ret;
ret = get_profiles(&profiles, &n);
if (ret == 0) {
size_t nfiltered;
struct profile *filtered = NULL;
ret = filter_profiles(profiles, n, filter, &filtered, &nfiltered);
printf("%zd\n", nfiltered);
free_profiles(filtered, nfiltered);
}
free_profiles(profiles, n);
return ret;
}
static int cmd_enabled(const char *command) {
int res = aa_is_enabled();
return res == 1 ? 0 : 1;
}
static int cmd_profiled(const char *command) {
return simple_filtered_count(NULL);
}
static int cmd_enforced(const char *command) {
return simple_filtered_count("enforce");
}
static int cmd_complaining(const char *command) {
return simple_filtered_count("complain");
}
static int compare_processes_by_profile(const void *a, const void *b) {
return strcmp(((struct process *)a)->profile,
((struct process *)b)->profile);
}
static int compare_processes_by_executable(const void *a, const void *b) {
return strcmp(((struct process *)a)->exe,
((struct process *)b)->exe);
}
static int detailed_output(int json) {
size_t nprofiles = 0, nprocesses = 0;
struct profile *profiles = NULL;
struct process *processes = NULL;
const char *profile_statuses[] = {"enforce", "complain"};
const char *process_statuses[] = {"enforce", "complain", "unconfined"};
int ret, i;
ret = get_profiles(&profiles, &nprofiles);
if (ret != 0) {
goto exit;
}
ret = get_processes(profiles, nprofiles, &processes, &nprocesses);
if (ret != 0) {
dfprintf(stderr, "Failed to get processes: %d....\n", ret);
goto exit;
}
if (json) {
printf("{\"version\": \"1\", \"profiles\": {");
} else {
dprintf("%zd profiles are loaded.\n", nprofiles);
}
for (i = 0; i < ARRAY_SIZE(profile_statuses); i++) {
size_t nfiltered = 0, j;
struct profile *filtered = NULL;
ret = filter_profiles(profiles, nprofiles, profile_statuses[i], &filtered, &nfiltered);
if (ret != 0) {
goto exit;
}
if (!json) {
dprintf("%zd profiles are in %s mode.\n", nfiltered, profile_statuses[i]);
}
for (j = 0; j < nfiltered; j++) {
if (json) {
printf("%s\"%s\": \"%s\"",
i == 0 && j == 0 ? "" : ", ", filtered[j].name, profile_statuses[i]);
} else {
dprintf(" %s\n", filtered[j].name);
}
}
free_profiles(filtered, nfiltered);
}
if (json) {
printf("}, \"processes\": {");
} else {
dprintf("%zd processes have profiles defined.\n", nprocesses);
}
for (i = 0; i < ARRAY_SIZE(process_statuses); i++) {
size_t nfiltered = 0, j;
struct process *filtered = NULL;
ret = filter_processes(processes, nprocesses, process_statuses[i], &filtered, &nfiltered);
if (ret != 0) {
goto exit;
}
if (!json) {
if (strcmp(process_statuses[i], "unconfined") == 0) {
dprintf("%zd processes are unconfined but have a profile defined.\n", nfiltered);
} else {
dprintf("%zd processes are in %s mode.\n", nfiltered, process_statuses[i]);
}
}
if (!json) {
qsort(filtered, nfiltered, sizeof(*filtered), compare_processes_by_profile);
for (j = 0; j < nfiltered; j++) {
dprintf(" %s (%s) %s\n", filtered[j].exe, filtered[j].pid,
// hide profile name if matches executable
(strcmp(filtered[j].profile, filtered[j].exe) == 0 ?
"" :
filtered[j].profile));
}
} else {
// json output requires processes to be grouped per executable
qsort(filtered, nfiltered, sizeof(*filtered), compare_processes_by_executable);
for (j = 0; j < nfiltered; j++) {
if (j > 0 && strcmp(filtered[j].exe, filtered[j - 1].exe) == 0) {
// same executable
printf(", {\"profile\": \"%s\", \"pid\": \"%s\", \"status\": \"%s\"}",
filtered[j].profile, filtered[j].pid, filtered[j].mode);
} else {
printf("%s\"%s\": [{\"profile\": \"%s\", \"pid\": \"%s\", \"status\": \"%s\"}",
// first element will be a unique executable
i == 0 && j == 0 ? "" : "], ",
filtered[j].exe, filtered[j].profile, filtered[j].pid, filtered[j].mode);
}
}
}
free_processes(filtered, nfiltered);
}
if (json) {
printf("%s}}", nprocesses > 0 ? "]" : "");
}
exit:
free_processes(processes, nprocesses);
free_profiles(profiles, nprofiles);
return ret == 0 ? (nprofiles > 0 ? AA_EXIT_ENABLED : AA_EXIT_NO_POLICY) : ret;
}
static int cmd_json(const char *command) {
detailed_output(1);
return 0;
}
static int cmd_pretty_json(const char *command) {
// TODO - add support for pretty printing json output
return cmd_json(command);
}
static int cmd_verbose(const char *command) {
verbose = 1;
return detailed_output(0);
}
static int print_usage(const char *command)
{
printf("Usage: %s [OPTIONS]\n"
"Displays various information about the currently loaded AppArmor policy.\n"
"OPTIONS (one only):\n"
" --enabled returns error code if AppArmor not enabled\n"
" --profiled prints the number of loaded policies\n"
" --enforced prints the number of loaded enforcing policies\n"
" --complaining prints the number of loaded non-enforcing policies\n"
" --json displays multiple data points in machine-readable JSON format\n"
" --pretty-json same data as --json, formatted for human consumption as well\n"
" --verbose (default) displays multiple data points about loaded policy set\n"
" --help this message\n",
command);
return 0;
}
struct command {
const char * const name;
int (*cmd)(const char *command);
};
static struct command commands[] = {
{"--enabled", cmd_enabled},
{"--profiled", cmd_profiled},
{"--enforced", cmd_enforced},
{"--complaining", cmd_complaining},
{"--json", cmd_json},
{"--pretty-json", cmd_pretty_json},
{"--verbose", cmd_verbose},
{"-v", cmd_verbose},
{"--help", print_usage},
{"-h", print_usage},
};
int main(int argc, char **argv)
{
int ret = EXIT_SUCCESS;
int _ret;
int (*cmd)(const char*) = cmd_verbose;
if (argc > 2) {
dfprintf(stderr, "Error: Too many options.\n");
cmd = print_usage;
ret = EXIT_FAILURE;
} else if (argc == 2) {
int (*_cmd)(const char*) = NULL;
int i;
for (i = 0; i < ARRAY_SIZE(commands); i++) {
if (strcmp(argv[1], commands[i].name) == 0) {
_cmd = commands[i].cmd;
break;
}
}
if (_cmd == NULL) {
dfprintf(stderr, "Error: Invalid command.\n");
cmd = print_usage;
ret = EXIT_FAILURE;
} else {
cmd = _cmd;
}
}
_ret = cmd(argv[0]);
exit(ret == EXIT_FAILURE ? ret : _ret);
}

View File

@ -22,7 +22,7 @@ include $(COMMONDIR)/Make.rules
PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \ PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \
aa-autodep aa-audit aa-complain aa-enforce aa-disable \ aa-autodep aa-audit aa-complain aa-enforce aa-disable \
aa-notify aa-status aa-unconfined aa-notify aa-unconfined
TOOLS = ${PYTOOLS} aa-decode aa-remove-unknown TOOLS = ${PYTOOLS} aa-decode aa-remove-unknown
PYSETUP = python-tools-setup.py PYSETUP = python-tools-setup.py
PYMODULES = $(wildcard apparmor/*.py apparmor/rule/*.py) PYMODULES = $(wildcard apparmor/*.py apparmor/rule/*.py)
@ -53,13 +53,11 @@ install: ${MANPAGES} ${HTMLMANPAGES}
install -d ${CONFDIR} install -d ${CONFDIR}
install -m 644 logprof.conf severity.db notify.conf ${CONFDIR} install -m 644 logprof.conf severity.db notify.conf ${CONFDIR}
install -d ${BINDIR} install -d ${BINDIR}
ln -sf aa-status ${BINDIR}/apparmor_status
# aa-easyprof is installed by python-tools-setup.py # aa-easyprof is installed by python-tools-setup.py
install -m 755 $(filter-out aa-easyprof, ${TOOLS}) ${BINDIR} install -m 755 $(filter-out aa-easyprof, ${TOOLS}) ${BINDIR}
$(MAKE) -C po install DESTDIR=${DESTDIR} NAME=${NAME} $(MAKE) -C po install DESTDIR=${DESTDIR} NAME=${NAME}
$(MAKE) install_manpages DESTDIR=${DESTDIR} $(MAKE) install_manpages DESTDIR=${DESTDIR}
$(MAKE) -C vim install DESTDIR=${DESTDIR} $(MAKE) -C vim install DESTDIR=${DESTDIR}
ln -sf aa-status.8 ${DESTDIR}/${MANDIR}/man8/apparmor_status.8
${PYTHON} ${PYSETUP} install --prefix=${PYPREFIX} --root=${DESTDIR} --version=${VERSION} ${PYTHON} ${PYSETUP} install --prefix=${PYPREFIX} --root=${DESTDIR} --version=${VERSION}
.PHONY: clean .PHONY: clean

View File

@ -1,267 +0,0 @@
#! /usr/bin/python3
# ------------------------------------------------------------------
#
# Copyright (C) 2005-2006 Novell/SUSE
# Copyright (C) 2011 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
import re, os, sys, errno, json
# PLEASE NOTE: we try to keep aa-status as minimal as possible, for
# environments where installing all of the python utils and python
# apparmor module may not make sense. Please think carefully before
# importing anything from apparmor; see how the apparmor.fail import is
# handled below.
# setup exception handling
try:
from apparmor.fail import enable_aa_exception_handler
enable_aa_exception_handler()
except ImportError:
# just let normal python exceptions happen (LP: #1480492)
pass
def cmd_enabled():
'''Returns error code if AppArmor is not enabled'''
if get_profiles() == {}:
sys.exit(2)
def cmd_profiled():
'''Prints the number of loaded profiles'''
profiles = get_profiles()
sys.stdout.write("%d\n" % len(profiles))
if profiles == {}:
sys.exit(2)
def cmd_enforced():
'''Prints the number of loaded enforcing profiles'''
profiles = get_profiles()
sys.stdout.write("%d\n" % len(filter_profiles(profiles, 'enforce')))
if profiles == {}:
sys.exit(2)
def cmd_complaining():
'''Prints the number of loaded non-enforcing profiles'''
profiles = get_profiles()
sys.stdout.write("%d\n" % len(filter_profiles(profiles, 'complain')))
if profiles == {}:
sys.exit(2)
def cmd_verbose():
'''Displays multiple data points about loaded profile set'''
global verbose
verbose = True
profiles = get_profiles()
processes = get_processes(profiles)
stdmsg("%d profiles are loaded." % len(profiles))
for status in ('enforce', 'complain'):
filtered_profiles = filter_profiles(profiles, status)
stdmsg("%d profiles are in %s mode." % (len(filtered_profiles), status))
for item in filtered_profiles:
stdmsg(" %s" % item)
stdmsg("%d processes have profiles defined." % len(processes))
for status in ('enforce', 'complain', 'unconfined'):
filtered_processes = filter_processes(processes, status)
if status == 'unconfined':
stdmsg("%d processes are unconfined but have a profile defined." % len(filtered_processes))
else:
stdmsg("%d processes are in %s mode." % (len(filtered_processes), status))
# Sort by name, and then by pid
filtered_processes.sort(key=lambda x: int(x[0]))
filtered_processes.sort(key=lambda x: x[1])
for (pid, profile, exe) in filtered_processes:
if exe == profile:
profile = ""
stdmsg(" %s (%s) %s" % (exe, pid, profile))
if profiles == {}:
sys.exit(2)
def cmd_json(pretty_output=False):
'''Outputs multiple data points about loaded profile set in a machine-readable JSON format'''
global verbose
profiles = get_profiles()
processes = get_processes(profiles)
i = {
'version': '1',
'profiles': {},
'processes': {}
}
for status in ('enforce', 'complain'):
filtered_profiles = filter_profiles(profiles, status)
for item in filtered_profiles:
i['profiles'][item] = status
for status in ('enforce', 'complain', 'unconfined'):
filtered_processes = filter_processes(processes, status)
for (pid, profile, exe) in filtered_processes:
if exe not in i['processes']:
i['processes'][exe] = []
i['processes'][exe].append({
'profile': profile,
'pid': pid,
'status': status
})
if pretty_output:
sys.stdout.write(json.dumps(i, sort_keys=True, indent=4, separators=(',', ': ')))
else:
sys.stdout.write(json.dumps(i))
def cmd_pretty_json():
cmd_json(True)
def get_profiles():
'''Fetch loaded profiles'''
profiles = {}
if os.path.exists("/sys/module/apparmor"):
stdmsg("apparmor module is loaded.")
else:
errormsg("apparmor module is not loaded.")
sys.exit(1)
apparmorfs = find_apparmorfs()
if not apparmorfs:
errormsg("apparmor filesystem is not mounted.")
sys.exit(3)
apparmor_profiles = os.path.join(apparmorfs, "profiles")
try:
f = open(apparmor_profiles)
except IOError as e:
if e.errno == errno.EACCES:
errormsg("You do not have enough privilege to read the profile set.")
else:
errormsg("Could not open %s: %s" % (apparmor_profiles, os.strerror(e.errno)))
sys.exit(4)
for p in f.readlines():
match = re.search("^(.+)\s+\((\w+)\)$", p)
profiles[match.group(1)] = match.group(2)
f.close()
return profiles
def get_processes(profiles):
'''Fetch process list'''
processes = {}
contents = os.listdir("/proc")
for filename in contents:
if filename.isdigit():
try:
for p in open("/proc/%s/attr/current" % filename).readlines():
match = re.search("^([^\(]+)\s+\((\w+)\)$", p)
exe = os.path.realpath("/proc/%s/exe" % filename)
if match:
processes[filename] = { 'profile' : match.group(1), \
'exe': exe, \
'mode' : match.group(2) }
elif exe in profiles:
# keep only unconfined processes that have a profile defined
processes[filename] = { 'profile' : exe, \
'exe': exe, \
'mode' : 'unconfined' }
except:
pass
return processes
def filter_profiles(profiles, status):
'''Return a list of profiles that have a particular status'''
filtered = []
for key, value in list(profiles.items()):
if value == status:
filtered.append(key)
filtered.sort()
return filtered
def filter_processes(processes, status):
'''Return a list of processes that have a particular status'''
filtered = []
for key, value in list(processes.items()):
if value['mode'] == status:
filtered.append([key, value['profile'], value['exe']])
return filtered
def find_apparmorfs():
'''Finds AppArmor mount point'''
for p in open("/proc/mounts", "rb").readlines():
if p.split()[2].decode() == "securityfs" and \
os.path.exists(os.path.join(p.split()[1].decode(), "apparmor")):
return os.path.join(p.split()[1].decode(), "apparmor")
return False
def errormsg(message):
'''Prints to stderr if verbose mode is on'''
global verbose
if verbose:
sys.stderr.write(message + "\n")
def stdmsg(message):
'''Prints to stdout if verbose mode is on'''
global verbose
if verbose:
sys.stdout.write(message + "\n")
def print_usage():
'''Print usage information'''
sys.stdout.write('''Usage: %s [OPTIONS]
Displays various information about the currently loaded AppArmor policy.
OPTIONS (one only):
--enabled returns error code if AppArmor not enabled
--profiled prints the number of loaded policies
--enforced prints the number of loaded enforcing policies
--complaining prints the number of loaded non-enforcing policies
--json displays multiple data points in machine-readable JSON format
--pretty-json same data as --json, formatted for human consumption as well
--verbose (default) displays multiple data points about loaded policy set
--help this message
''' % sys.argv[0])
# Main
global verbose
verbose = False
if len(sys.argv) > 2:
sys.stderr.write("Error: Too many options.\n")
print_usage()
sys.exit(1)
elif len(sys.argv) == 2:
cmd = sys.argv.pop(1)
else:
cmd = '--verbose'
# Command dispatch:
commands = {
'--enabled' : cmd_enabled,
'--profiled' : cmd_profiled,
'--enforced' : cmd_enforced,
'--complaining' : cmd_complaining,
'--json' : cmd_json,
'--pretty-json' : cmd_pretty_json,
'--verbose' : cmd_verbose,
'-v' : cmd_verbose,
'--help' : print_usage,
'-h' : print_usage
}
if cmd in commands:
commands[cmd]()
sys.exit(0)
else:
sys.stderr.write("Error: Invalid command.\n")
print_usage()
sys.exit(1)