Skip to content

Linux 内核 AppArmor 安全模块详细分析

目录

  1. AppArmor 概述
  2. AppArmor 策略
  3. 文件权限
  4. 任务上下文
  5. 网络控制
  6. LSM 钩子集成
  7. 架构图

1. AppArmor 概述

1.1 基于路径的 MAC

AppArmor (Application Armor) 是一个基于路径的强制访问控制 (MAC) 安全模块,与传统的基于标签的 MAC 系统(如 SELinux)有本质区别。

核心设计理念:

  • 路径匹配: AppArmor 通过文件的完整路径而非 inode 或设备号来标识文件
  • 配置文件 (Profile): 每个应用程序关联一个配置文件,定义其允许访问的资源
  • 渐进式安全: 支持 complain 模式(学习模式)和 enforce 模式(强制模式)

1.2 与 SELinux 的区别

特性AppArmorSELinux
标识方式文件路径SELinux 上下文 (user:role:type)
策略语言文本配置SELinux 策略语言 (基于 MLS/MCS)
复杂度相对简单复杂但功能强大
学习模式原生支持 (complain 模式)需要 setroubleshoot
文件系统覆盖仅支持 AppArmor 标记的文件系统支持所有文件系统
默认策略允许所有,未列出的权限默认拒绝拒绝所有

1.3 核心数据结构

源码位置: /Users/sphinx/github/linux/security/apparmor/include/

c
// apparmor.h (第19-43行)
//  mediation 类别定义
#define AA_CLASS_FILE       2   // 文件类
#define AA_CLASS_CAP        3   // 能力类
#define AA_CLASS_NET       14   // 网络类
#define AA_CLASS_NS        21   // 命名空间类
#define AA_CLASS_DBUS      32   // DBus 类

// policy.h (第258-282行)
// aa_profile - 基本限制数据结构
struct aa_profile {
    struct aa_policy base;           // 基础组件 (名称、引用计数、列表)
    struct aa_profile __rcu *parent; // 父 profile (支持 profile 继承/hats)
    struct aa_ns *ns;               // 所属命名空间
    enum audit_mode audit;          // 审计模式
    long mode;                      // 执行模式 (enforce/complain/kill/unconfined)
    u32 path_flags;                 // 路径标志
    struct aa_attachment attach;     // 附件规则
    struct aa_loaddata *rawdata;    // 原始策略数据
    int n_rules;
    struct aa_label label;          // 标签 (包含规则向量)
};

// label.h (第127-148行)
// aa_label - 标签结构 (可以包含多个 profile)
struct aa_label {
    struct aa_common_ref count;      // 引用计数
    struct rb_node node;            // 红黑树节点
    struct aa_proxy *proxy;         // 代理 (用于 label 更新)
    __counted char *hname;          // 人类可读的标签名
    long flags;                     // 标志 (FLAG_HAT, FLAG_UNCONFINED, FLAG_NULL 等)
    u32 secid;                      // 安全标识符
    int size;                       // vec 数组中的条目数
    u64 mediates;                   // 位掩码,表示该 label 调解的类别
    union {
        struct {
            struct aa_profile *profile[2];  // 内嵌在 profile 中时使用
            DECLARE_FLEX_ARRAY(struct aa_ruleset *, rules);
        };
        DECLARE_FLEX_ARRAY(struct aa_profile *, vec);  // 复合标签使用
    };
};

2. AppArmor 策略

2.1 struct aa_policy: 策略结构

源码位置: /Users/sphinx/github/linux/security/apparmor/policy.c

c
// policy.h (第82-121行)
// aa_policydb - 策略匹配引擎
struct aa_policydb {
    struct kref count;              // 引用计数
    struct aa_dfa *dfa;             // DFA 模式匹配
    struct {
        struct aa_perms *perms;     // 权限表
        u32 size;                   // 权限条目数
    };
    struct aa_str_table trans;      // 转换表
    struct aa_tags_struct tags;     // 标签表
    aa_state_t start[AA_CLASS_LAST + 1];  // 每种类别的起始状态
};

// aa_ruleset - 规则集 (第181-203行)
struct aa_ruleset {
    int size;
    struct aa_policydb *policy;      // 通用策略规则
    struct aa_policydb *file;       // 文件访问和域转换规则
    struct aa_caps caps;            // 能力规则
    struct aa_rlimit rlimits;       // 资源限制规则
    int secmark_count;
    struct aa_secmark *secmark;     // 安全标记
};

2.2 struct profile: 配置文件

Profile 层次结构:

Namespace (命名空间)
  |
  +-- unconfined profile (无限制配置)
  |
  +-- profile // 子 profile
  |     |
  |     +-- hat (帽子/子配置)
  |     +-- null-XXX (学习模式配置)
  |
  +-- profile // 另一个配置

Profile 分配 (policy.c 第367-412行):

c
struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
                                   gfp_t gfp)
{
    struct aa_profile *profile;

    // 分配 flexible array, 为 label.rules 预留空间
    profile = kzalloc_flex(*profile, label.rules, 1, gfp);
    if (!profile)
        return NULL;

    // 初始化基础策略结构
    if (!aa_policy_init(&profile->base, NULL, hname, gfp))
        goto fail;
    // 初始化标签
    if (!aa_label_init(&profile->label, 1, gfp))
        goto fail;

    // 分配第一个规则集
    profile->label.rules[0] = aa_alloc_ruleset(gfp);
    if (!profile->label.rules[0])
        goto fail;
    profile->n_rules = 1;

    // 设置 proxy (用于 label 版本管理)
    if (!proxy) {
        proxy = aa_alloc_proxy(&profile->label, gfp);
        if (!proxy)
            goto fail;
    } else
        aa_get_proxy(proxy);
    profile->label.proxy = proxy;

    profile->label.hname = profile->base.hname;
    profile->label.flags |= FLAG_PROFILE;  // 标记为 profile
    profile->label.vec[0] = profile;

    profile->signal = SIGKILL;  // kill 模式下使用的信号

    return profile;

fail:
    aa_free_profile(profile);
    return NULL;
}

2.3 aa_load_policy(): 策略加载

aa_replace_profiles() (policy.c 第1162-1408行)

c
ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
                            u32 mask, struct aa_loaddata *udata)
{
    // 策略加载流程:
    // 1. 解包序列化数据
    //    error = aa_unpack(udata, &lh, &ns_name);

    // 2. 验证所有 profile 属于同一命名空间
    //    list_for_each_entry(ent, &lh, list) { ... }

    // 3. 准备命名空间
    //    ns = aa_prepare_ns(policy_ns ? policy_ns : labels_ns(label), ns_name);

    // 4. 查找并替换/添加 profile
    //    mutex_lock_nested(&ns->lock, ns->level);
    //    list_for_each_entry(ent, &lh, list) {
    //        error = __lookup_replace(ns, ent->new->base.hname, ...);
    //        // 设置父 profile 和命名空间
    //        ent->new->ns = aa_get_ns(ns);
    //    }

    // 5. 更新命名空间版本号
    //    __aa_bump_ns_revision(ns);

    // 6. 执行替换或添加
    //    list_for_each_entry_safe(ent, tmp, &lh, list) {
    //        if (ent->old) {
    //            share_name(ent->old, ent->new);
    //            __replace_profile(ent->old, ent->new);
    //        } else {
    //            __add_profile(lh, ent->new);
    //        }
    //    }

    // 7. 更新标签集子树
    //    __aa_labelset_update_subtree(ns);
}

策略管理权限检查 (policy.c 第971-1009行):

c
int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label,
                         struct aa_ns *ns, const struct cred *ocred, u32 mask)
{
    // 1. 检查策略是否被锁定
    if (aa_g_lock_policy)
        return audit_policy(label, op, NULL, NULL, "policy_locked", -EACCES);

    // 2. 检查对象权限
    if (ocred && !is_subset_of_obj_privilege(subj_cred, label, ocred))
        return audit_policy(label, op, NULL, NULL,
            "not privileged for target profile", -EACCES);

    // 3. 检查 MAC_ADMIN capability
    if (!aa_policy_admin_capable(subj_cred, label, ns))
        return audit_policy(label, op, NULL, NULL, "not policy admin", -EACCES);

    return 0;
}

3. 文件权限

3.1 aa_file_permission(): 文件权限检查

源码位置: /Users/sphinx/github/linux/security/apparmor/main.c

文件上下文缓存 (file.h 第37-46行):

c
// aa_file_ctx - 文件打开时的 AppArmor 上下文
struct aa_file_ctx {
    spinlock_t lock;                    // 锁
    struct aa_label __rcu *label;       // 缓存的文件标签
    u32 allow;                          // 允许的权限
};

aa_file_perm() (main.c 第619-671行):

c
int aa_file_perm(const char *op, const struct cred *subj_cred,
                 struct aa_label *label, struct file *file,
                 u32 request, bool in_atomic)
{
    struct aa_file_ctx *fctx;
    struct aa_label *flabel;
    u32 denied;
    int error = 0;

    // 获取文件的安全上下文
    fctx = file_ctx(file);
    rcu_read_lock();
    flabel = rcu_dereference(fctx->label);

    // 快速路径: 如果任务无限制或权限已缓存且足够,则跳过检查
    denied = request & ~fctx->allow;
    if (unconfined(label) || __file_is_delegated(flabel) ||
        (!denied && __is_unix_file(file) && !__unix_needs_revalidation(...)) ||
        (!denied && __aa_subj_label_is_cached(label, flabel))) {
        rcu_read_unlock();
        goto done;
    }

    // 慢速路径: 重新验证访问权限
    flabel = aa_get_newest_label(flabel);
    rcu_read_unlock();

    // 根据文件系统类型选择检查方法
    if (path_mediated_fs(file->f_path.dentry))
        error = __file_path_perm(op, subj_cred, label, flabel, file,
                                 request, denied, in_atomic);
    else if (S_ISSOCK(file_inode(file)->i_mode))
        error = __file_sock_perm(op, subj_cred, label, flabel, file,
                                  request, denied);

    aa_put_label(flabel);

done:
    return error;
}

__file_path_perm() (main.c 第483-542行):

c
static int __file_path_perm(const char *op, const struct cred *subj_cred,
                            struct aa_label *label, struct aa_label *flabel,
                            struct file *file, u32 request, u32 denied,
                            bool in_atomic)
{
    // 获取文件所有者信息
    vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(file),
                                        file_inode(file));
    struct path_cond cond = {
        .uid = vfsuid_into_kuid(vfsuid),
        .mode = file_inode(file)->i_mode
    };

    // 如果 label 是 flabel 的子集,且权限未被拒绝,则不需要重新检查
    if (!denied && aa_label_is_subset(flabel, label))
        return 0;

    // 检查每个 profile
    error = fn_for_each_not_in_set(flabel, label, profile,
            profile_path_perm(op, subj_cred, profile,
                              &file->f_path, buffer,
                              request, &cond, flags, &perms));

    // 更新文件上下文缓存
    if (!error)
        update_file_ctx(file_ctx(file), label, request);
}

aa_path_perm() (main.c 第280-302行):

c
int aa_path_perm(const char *op, const struct cred *subj_cred,
                 struct aa_label *label, const struct path *path, int flags,
                 u32 request, struct path_cond *cond)
{
    struct aa_perms perms = {};
    struct aa_profile *profile;
    char *buffer = NULL;
    int error;

    // 设置额外标志
    flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);

    buffer = aa_get_buffer(false);
    if (!buffer)
        return -ENOMEM;

    // 为 label 中的每个受限 profile 检查权限
    error = fn_for_each_confined(label, profile,
            profile_path_perm(op, subj_cred, profile, path, buffer,
                              request, cond, flags, &perms));

    aa_put_buffer(buffer);
    return error;
}

硬链接权限检查 (main.c 第430-460行):

c
int aa_path_link(const struct cred *subj_cred, struct aa_label *label,
                 struct dentry *old_dentry, const struct path *new_dir,
                 struct dentry *new_dentry)
{
    struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
    struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
    struct inode *inode = d_backing_inode(old_dentry);
    vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode);
    struct path_cond cond = {
        .uid = vfsuid_into_kuid(vfsuid),
        .mode = inode->i_mode,
    };

    // 检查链接和目标的路径权限
    error = fn_for_each_confined(label, profile,
            profile_path_link(subj_cred, profile, &link, buffer,
                              &target, buffer2, &cond));
}

profile_path_link() (main.c 第324-409行):

c
static int profile_path_link(const struct cred *subj_cred,
                             struct aa_profile *profile,
                             const struct path *link, char *buffer,
                             const struct path *target, char *buffer2,
                             struct path_cond *cond)
{
    // 1. 获取链接名称
    error = path_name(OP_LINK, subj_cred, &profile->label, link, ...);

    // 2. 获取目标名称
    error = path_name(OP_LINK, subj_cred, &profile->label, target, ...);

    // 3. 检查链接权限
    state = aa_str_perms(rules->file, ..., lname, cond, &lperms);
    if (!(lperms.allow & AA_MAY_LINK))
        goto audit;

    // 4. 从链接状态继续检查目标权限
    state = aa_dfa_null_transition(rules->file->dfa, state);
    aa_str_perms(rules->file, state, tname, cond, &perms);

    // 5. 子集测试: 链接权限必须是目标权限的子集
    if (perms.allow & AA_LINK_SUBSET) {
        request = lperms.allow & ~AA_MAY_LINK;
        lperms.allow &= perms.allow | AA_MAY_LINK;
        // 检查执行权限
        if ((lperms.allow & MAY_EXEC) &&
            !xindex_is_subset(lperms.xindex, perms.xindex)) {
            // 链接不是目标的子集
        }
    }
}

3.3 aa_path_rmdir(): 删除权限

通过 LSM 钩子集成 (lsm.c 第336-339行):

c
static int apparmor_path_rmdir(const struct path *dir, struct dentry *dentry)
{
    return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE);
}

common_perm_rm() (lsm.c 第286-301行):

c
static int common_perm_rm(const char *op, const struct path *dir,
                          struct dentry *dentry, u32 mask)
{
    struct inode *inode = d_backing_inode(dentry);
    struct path_cond cond = {};
    vfsuid_t vfsuid;

    if (!inode || !path_mediated_fs(dentry))
        return 0;

    vfsuid = i_uid_into_vfsuid(mnt_idmap(dir->mnt), inode);
    cond.uid = vfsuid_into_kuid(vfsuid);
    cond.mode = inode->i_mode;

    return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
}

4. 任务上下文

4.1 aa_bprm_set_creds(): exec 时设置凭证

源码位置: /Users/sphinx/github/linux/security/apparmor/domain.c

apparmor_bprm_creds_for_exec() (domain.c 第919-1046行):

c
int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
{
    struct aa_task_ctx *ctx;
    struct aa_label *label, *new = NULL;
    const struct cred *subj_cred;
    char *buffer = NULL;
    int error = 0;
    bool unsafe = false;

    subj_cred = current_cred();
    ctx = task_ctx(current);

    label = aa_get_newest_label(cred_label(bprm->cred));

    // 检测 no_new_privs 并存储当时的 label
    if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) &&
        !ctx->nnp)
        ctx->nnp = aa_get_label(label);

    // 先检查 onexec 转换
    if (ctx->onexec)
        new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token,
                            bprm, buffer, &cond, &unsafe);
    else
        new = fn_label_build(label, profile, GFP_KERNEL,
                profile_transition(subj_cred, profile, bprm,
                                   buffer, &cond, &unsafe));

    // no_new_privs 检查: 确保转换后的域是之前域的子集
    if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
        !unconfined(label) &&
        !aa_label_is_unconfined_subset(new, ctx->nnp)) {
        error = -EPERM;
        info = "no new privs";
        goto audit;
    }

    // ptrace 检查
    if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
        error = may_change_ptraced_domain(bprm->cred, new, &info);
        if (error)
            goto audit;
    }

    // 设置 AT_SECURE 标志
    if (unsafe)
        bprm->secureexec = 1;

    // 更新 bprm 的 cred label
    aa_put_label(cred_label(bprm->cred));
    set_cred_label(bprm->cred, new);
}

profile_transition() (domain.c 第659-790行):

c
static struct aa_label *profile_transition(const struct cred *subj_cred,
                                           struct aa_profile *profile,
                                           const struct linux_binprm *bprm,
                                           char *buffer, struct path_cond *cond,
                                           bool *secure_exec)
{
    // 1. 解析可执行文件路径
    error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
                         &name, &info, profile->disconnected);

    // 2. 如果是无限制 profile,查找附件配置
    if (profile_unconfined(profile)) {
        new = find_attach(bprm, profile->ns, &profile->ns->base.profiles,
                          name, &info);
        // ...
    }

    // 3. 查找 exec 权限
    state = aa_str_perms(rules->file, state, name, cond, &perms);

    // 4. 确定转换目标
    if (perms.allow & MAY_EXEC) {
        new = x_to_label(profile, bprm, name, perms.xindex, &target, &info);
        // ...
    }

    // 5. 如果是不安全 exec,设置 secure_exec 标志
    if (!(perms.xindex & AA_X_UNSAFE)) {
        *secure_exec = true;
    }
}

4.2 aa_task_perm(): 任务权限

任务上下文结构 (task.h 第25-30行):

c
// aa_task_ctx - 任务标签变更信息
struct aa_task_ctx {
    struct aa_label *nnp;       // no_new_privs 时的快照
    struct aa_label *onexec;    // 下一个 exec 时的转换目标
    struct aa_label *previous;  // 可返回的前一个 profile (用于 change_hat)
    u64 token;                  // change_hat 的魔数
};

change_hat() - 子域/帽子切换 (domain.c 第1107-1204行):

c
static struct aa_label *change_hat(const struct cred *subj_cred,
                                   struct aa_label *label, const char *hats[],
                                   int count, int flags)
{
    // 在当前 label 中查找匹配的帽子
    for (i = 0; i < count && !hat; i++) {
        name = hats[i];
        label_for_each_in_scope(it, labels_ns(label), label, profile) {
            hat = aa_find_child(root, name);
            if (!hat && COMPLAIN_MODE(profile)) {
                // 创建学习模式的 null profile
                hat = aa_new_learning_profile(profile, true, name, GFP_KERNEL);
            }
        }
    }

    // 构建新的复合 label
    new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
                   build_change_hat(subj_cred, profile, name, sibling),
                   aa_get_label(&profile->label));
}

aa_change_hat() (domain.c 第1223-1349行):

c
int aa_change_hat(const char *hats[], int count, u64 token, int flags)
{
    // 1. 获取当前 label
    label = aa_get_newest_cred_label(subj_cred);
    previous = aa_get_newest_label(ctx->previous);

    // 2. 检测 no_new_privs
    if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
        ctx->nnp = aa_get_label(label);

    // 3. 如果 count > 0,切换到帽子
    if (count) {
        new = change_hat(subj_cred, label, hats, count, flags);
        error = may_change_ptraced_domain(subj_cred, new, &info);
        if (error)
            goto fail;
        target = new;
        error = aa_set_current_hat(new, token);
    }
    // 4. 如果 count == 0,恢复到之前的 profile
    else if (previous && !(flags & AA_CHANGE_TEST)) {
        target = previous;
        error = aa_restore_previous_label(token);
    }
}

5. 网络控制

5.1 aa_sock_perm(): 套接字权限

源码位置: /Users/sphinx/github/linux/security/apparmor/net.c

aa_sk_perm() (net.c 第306-320行):

c
int aa_sk_perm(const char *op, u32 request, struct sock *sk)
{
    struct aa_label *label;
    int error;

    label = begin_current_label_crit_section();
    error = aa_label_sk_perm(current_cred(), label, op, request, sk);
    end_current_label_crit_section(label);

    return error;
}

aa_label_sk_perm() (net.c 第283-304行):

c
static int aa_label_sk_perm(const struct cred *subj_cred,
                            struct aa_label *label,
                            const char *op, u32 request,
                            struct sock *sk)
{
    struct aa_sk_ctx *ctx = aa_sock(sk);
    int error = 0;

    // 检查 socket 是否关联了非 kernel_t 的标签
    if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) {
        struct aa_profile *profile;
        DEFINE_AUDIT_SK(ad, op, subj_cred, sk);

        ad.subj_cred = subj_cred;
        error = fn_for_each_confined(label, profile,
                aa_profile_af_sk_perm(profile, &ad, request, sk));
    }

    return error;
}

aa_profile_af_perm() (net.c 第250-270行):

c
int aa_profile_af_perm(struct aa_profile *profile,
                       struct apparmor_audit_data *ad, u32 request,
                       u16 family, int type, int protocol)
{
    struct aa_ruleset *rules = profile->label.rules[0];
    struct aa_perms *p = NULL;
    aa_state_t state;

    if (profile_unconfined(profile))
        return 0;

    // 获取网络调解的起始状态
    state = RULE_MEDIATES_NET(rules);
    if (!state)
        return 0;

    // 匹配地址族、类型、协议
    state = aa_match_to_prot(rules->policy, state, request, family, type,
                             protocol, &p, &ad->info);
    return aa_do_perms(profile, rules->policy, state, request, p, ad);
}

aa_match_to_prot() (net.c 第224-247行):

c
aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state,
                            u32 request, u16 af, int type, int protocol,
                            struct aa_perms **p, const char **info)
{
    // 1. 匹配地址族
    state = aa_dfa_match_be16(policy->dfa, state, (u16)af);
    if (!state) {
        *info = "failed af match";
        return state;
    }

    // 2. 匹配 socket 类型
    state = aa_dfa_match_be16(policy->dfa, state, (u16)type);
    if (state) {
        if (p)
            *p = early_match(policy, state, request);
        if (!p || !*p) {
            // 3. 匹配协议
            state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol);
            if (!state)
                *info = "failed protocol match";
        }
    } else {
        *info = "failed type match";
    }

    return state;
}

网络权限类别 (apparmor.h 第30-31行):

c
#define AA_CLASS_NET    14   // 网络类 (v7/v8)
#define AA_CLASS_NETV9  15   // 网络类 (v9)

网络权限掩码 (net.c 第34-74行):

c
static const char * const net_mask_names[] = {
    "unknown",
    "send",              // AA_MAY_SEND
    "receive",           // AA_MAY_RECEIVE
    "unknown",
    "create",            // AA_MAY_CREATE
    "shutdown",          // AA_MAY_SHUTDOWN
    "connect",          // AA_MAY_CONNECT
    "unknown",
    "setattr",          // AA_MAY_SETATTR
    "getattr",          // AA_MAY_GETATTR
    "setcred",          // AA_MAY_SETCREDS
    "getcred",          // AA_MAY_GETCREDS
    // ...
    "accept",           // AA_MAY_ACCEPT
    "bind",             // AA_MAY_BIND
    "listen",           // AA_MAY_LISTEN
    // ...
    "setopt",           // AA_MAY_SETOPT
    "getopt",           // AA_MAY_GETOPT
};

Unix 域套接字特殊处理 (lsm.c 第1366-1369行):

c
static int apparmor_socket_bind(struct socket *sock,
                                struct sockaddr *address, int addrlen)
{
    if (sock->sk->sk_family == PF_UNIX)
        return aa_unix_bind_perm(sock, address, addrlen);
    return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk);
}

6. LSM 钩子集成

6.1 钩子注册

源码位置: /Users/sphinx/github/linux/security/apparmor/lsm.c

安全钩子列表 (lsm.c 第1664-1769行):

c
static struct security_hook_list apparmor_hooks[] __ro_after_init = {
    // PTrace 钩子
    LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check),
    LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme),

    // Capability 钩子
    LSM_HOOK_INIT(capget, apparmor_capget),
    LSM_HOOK_INIT(capable, apparmor_capable),

    // 文件系统钩子
    LSM_HOOK_INIT(sb_mount, apparmor_sb_mount),
    LSM_HOOK_INIT(sb_umount, apparmor_sb_umount),
    LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot),
    LSM_HOOK_INIT(move_mount, apparmor_move_mount),

    // Path 钩子
    LSM_HOOK_INIT(path_link, apparmor_path_link),
    LSM_HOOK_INIT(path_unlink, apparmor_path_unlink),
    LSM_HOOK_INIT(path_symlink, apparmor_path_symlink),
    LSM_HOOK_INIT(path_mkdir, apparmor_path_mkdir),
    LSM_HOOK_INIT(path_rmdir, apparmor_path_rmdir),
    LSM_HOOK_INIT(path_mknod, apparmor_path_mknod),
    LSM_HOOK_INIT(path_rename, apparmor_path_rename),
    LSM_HOOK_INIT(path_chmod, apparmor_path_chmod),
    LSM_HOOK_INIT(path_chown, apparmor_path_chown),
    LSM_HOOK_INIT(path_truncate, apparmor_path_truncate),

    // File 钩子
    LSM_HOOK_INIT(file_open, apparmor_file_open),
    LSM_HOOK_INIT(file_receive, apparmor_file_receive),
    LSM_HOOK_INIT(file_permission, apparmor_file_permission),
    LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security),
    LSM_HOOK_INIT(file_free_security, apparmor_file_free_security),
    LSM_HOOK_INIT(mmap_file, apparmor_mmap_file),
    LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect),
    LSM_HOOK_INIT(file_lock, apparmor_file_lock),
    LSM_HOOK_INIT(file_truncate, apparmor_file_truncate),

    // Socket 钩子
    LSM_HOOK_INIT(socket_create, apparmor_socket_create),
    LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create),
    LSM_HOOK_INIT(socket_bind, apparmor_socket_bind),
    LSM_HOOK_INIT(socket_connect, apparmor_socket_connect),
    LSM_HOOK_INIT(socket_listen, apparmor_socket_listen),
    LSM_HOOK_INIT(socket_accept, apparmor_socket_accept),
    LSM_HOOK_INIT(socket_sendmsg, apparmor_socket_sendmsg),
    LSM_HOOK_INIT(socket_recvmsg, apparmor_socket_recvmsg),
    LSM_HOOK_INIT(socket_getsockname, apparmor_socket_getsockname),
    LSM_HOOK_INIT(socket_getpeername, apparmor_socket_getpeername),
    LSM_HOOK_INIT(socket_getsockopt, apparmor_socket_getsockopt),
    LSM_HOOK_INIT(socket_setsockopt, apparmor_socket_setsockopt),
    LSM_HOOK_INIT(socket_shutdown, apparmor_socket_shutdown),
    LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect),
    LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send),

    // Task 钩子
    LSM_HOOK_INIT(bprm_creds_for_exec, apparmor_bprm_creds_for_exec),
    LSM_HOOK_INIT(bprm_committing_creds, apparmor_bprm_committing_creds),
    LSM_HOOK_INIT(bprm_committed_creds, apparmor_bprm_committed_creds),
    LSM_HOOK_INIT(task_free, apparmor_task_free),
    LSM_HOOK_INIT(task_alloc, apparmor_task_alloc),
    LSM_HOOK_INIT(task_kill, apparmor_task_kill),
    LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit),

    // Credential 钩子
    LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank),
    LSM_HOOK_INIT(cred_free, apparmor_cred_free),
    LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare),
    LSM_HOOK_INIT(cred_transfer, apparmor_cred_transfer),

    // ... 更多钩子
};

6.2 安全 blob 布局

c
// lsm.c (第1652-1657行)
struct lsm_blob_sizes apparmor_blob_sizes __ro_after_init = {
    .lbs_cred = sizeof(struct aa_label *),      // credentials 中的 label 指针
    .lbs_file = sizeof(struct aa_file_ctx),     // 文件安全上下文
    .lbs_task = sizeof(struct aa_task_ctx),     // 任务上下文
    .lbs_sock = sizeof(struct aa_sk_ctx),      // socket 上下文
};

6.3 AppArmor 初始化

apparmor_init() (lsm.c 第2507-2567行):

c
static int __init apparmor_init(void)
{
    int error;

    // 1. 设置 DFA 引擎
    error = aa_setup_dfa_engine();
    if (error) {
        AA_ERROR("Unable to setup dfa engine\n");
        goto alloc_out;
    }

    // 2. 分配根命名空间
    error = aa_alloc_root_ns();
    if (error) {
        AA_ERROR("Unable to allocate default profile namespace\n");
        goto alloc_out;
    }

    // 3. 注册 sysctl
    error = apparmor_init_sysctl();

    // 4. 分配缓冲区
    error = alloc_buffers();

    // 5. 设置 init 任务的上下文
    error = set_init_ctx();

    // 6. 注册安全钩子
    security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks),
                      &apparmor_lsmid);

    apparmor_initialized = 1;

    return error;
}

// 定义 LSM
DEFINE_LSM(apparmor) = {
    .id = &apparmor_lsmid,
    .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
    .enabled = &apparmor_enabled,
    .blobs = &apparmor_blob_sizes,
    .init = apparmor_init,
    // ...
};

7. 架构图

7.1 AppArmor 整体架构

+-------------------+     +-------------------+     +-------------------+
|   User Space      |     |   Kernel Space    |     |   Policy Store    |
|                   |     |                   |     |                   |
| +---------------+ |     | +---------------+ |     | +---------------+ |
| |   Application | |     | |   LSM Hooks   | |     | | /sys/kernel/  | |
| |   (Profile)   |----->| | (apparmor.c)  | |     | | security/     | |
| +---------------+ |     | +---------------+ |     | | apparmor/     | |
|                   |     |        |          |     | +---------------+ |
| +---------------+ |     |        v          |     |                   |
| |   aa-status   | |     | +---------------+ |     | +---------------+ |
| |   aa-complain | |     | |   Policy      | |     | |   Policy      | |
| |   aa-enforce  | |     | |   Engine     | |     | |   Binary      | |
| +---------------+ |     | | (policy.c)   | |     | |   (packed)    | |
|                   |     | +---------------+ |     | +---------------+ |
+-------------------+     |        |          |     +-------------------+
                         |        v          |
                         | +---------------+ |
                         | |   DFA Engine  | |
                         | |  (match.c)    | |
                         | +---------------+ |
                         |        |          |
                         |        v          |
                         | +---------------+ |
                         | |   Label       | |
                         | |   Management  | |
                         | |  (label.c)    | |
                         | +---------------+ |
                         +-------------------+

7.2 策略加载流程

User Space                  Kernel Space                    Data Structures
-----------                 ------------                    --------------

aa_load_policy()
     |
     |  ioctl(fd, POLICY_LOAD, policy_data)
     v
apparmor_update_policy()
     |
     +---> aa_may_manage_policy()
     |           |
     |           +---> capability check (CAP_MAC_ADMIN)
     |           +---> aa_policy_admin_capable()
     |
     +---> aa_unpack()
     |           |
     |           +---> 解析序列化的策略数据
     |           +---> 创建 aa_profile 结构
     |           +---> 创建 aa_ruleset 结构
     |
     +---> aa_replace_profiles()
                 |
                 +---> __lookup_replace()
                 |           |
                 |           +---> 查找已存在的 profile
                 |           +---> 检查是否可替换
                 |
                 +---> __add_profile() 或 __replace_profile()
                             |
                             +---> 更新 namespace 的 profile 列表
                             +---> 更新 label 树
                             +---> __aa_labelset_update_subtree()

7.3 文件访问控制流程

Application                AppArmor LSM                    File System
-----------                --------------                    ------------

open("/path/to/file", O_RDONLY)
     |
     v
apparmor_file_open()
     |
     +---> aa_path_perm()
     |           |
     |           +---> path_name()        // 解析路径
     |           +---> __aa_path_perm()
     |                       |
     |                       +---> aa_str_perms()
     |                       |           |
     |                       |           +---> aa_dfa_match()  // DFA 匹配
     |                       |           +---> aa_lookup_condperms()
     |                       |
     |                       +---> aa_audit_file()
     |                                   |
     |                                   +---> 记录审计日志
     |
     +---> update_file_ctx()  // 缓存权限

7.4 域转换流程 (exec)

Application                AppArmor Domain                  Target Profile
-----------                ----------------                  --------------

execve("/bin/ls", ...)
     |
     v
apparmor_bprm_creds_for_exec()
     |
     +---> profile_transition()
     |           |
     |           +---> aa_path_name()           // 获取可执行文件路径
     |           +---> aa_str_perms()           // 检查 MAY_EXEC
     |           +---> x_to_label()             // 确定目标 label
     |                       |
     |                       +---> find_attach()      // 查找附件 profile
     |                       +---> x_table_lookup()  // 查转换表
     |
     +---> may_change_ptraced_domain()   // 检查 ptrace 限制
     |
     +---> set_cred_label()             // 更新 bprm->cred

7.5 网络访问控制流程

Application                AppArmor Network                 Policy Match
-----------                ----------------                 -------------

socket(PF_INET, SOCK_STREAM, 0)
     |
     v
apparmor_socket_create()
     |
     +---> aa_af_perm()
     |           |
     |           +---> aa_profile_af_perm()
     |                       |
     |                       +---> RULE_MEDIATES_NET()    // 获取起始状态
     |                       +---> aa_match_to_prot()
     |                                   |
     |                                   +---> aa_dfa_match_be16(af)    // 匹配地址族
     |                                   +---> aa_dfa_match_be16(type)  // 匹配类型
     |                                   +---> aa_dfa_match_be16(proto)// 匹配协议
     |
     +---> aa_do_perms()
                 |
                 +---> aa_check_perms()

附录 A: 关键文件索引

文件功能
/security/apparmor/lsm.cLSM 钩子注册和主入口
/security/apparmor/policy.c策略加载和管理
/security/apparmor/domain.c域转换和 exec 钩子
/security/apparmor/main.c文件权限检查
/security/apparmor/task.c任务上下文管理
/security/apparmor/net.c网络权限检查
/security/apparmor/include/policy.h策略数据结构
/security/apparmor/include/label.h标签数据结构
/security/apparmor/include/file.h文件权限数据结构
/security/apparmor/include/task.h任务上下文结构
/security/apparmor/include/apparmor.h类别定义和常量

附录 B: 权限标志

c
// 文件权限 (file.h)
#define AA_MAY_CREATE    0x0010   // 创建
#define AA_MAY_DELETE    0x0020   // 删除
#define AA_MAY_GETATTR   0x0100   // 获取属性
#define AA_MAY_SETATTR   0x0200   // 设置属性
#define AA_MAY_CHMOD      0x0400   // 修改权限
#define AA_MAY_CHOWN      0x0800   // 修改所有者
#define AA_MAY_LOCK       0x1000   // 文件锁
#define AA_EXEC_MMAP      0x2000   // mmap 执行
#define AA_MAY_LINK       0x4000   // 硬链接

// 网络权限
#define AA_MAY_SEND       0x0002   // 发送
#define AA_MAY_RECEIVE    0x0004   // 接收
#define AA_MAY_CREATE     0x0010   // 创建套接字
#define AA_MAY_SHUTDOWN   0x0020   // 关闭
#define AA_MAY_CONNECT    0x0040   // 连接
#define AA_MAY_BIND       0x0400   // 绑定地址
#define AA_MAY_LISTEN     0x0800   // 监听
#define AA_MAY_ACCEPT     0x1000   // 接受连接

文档生成时间: 2026-04-26分析基于 Linux 内核源码

基于 VitePress 构建