Linux Kernel LSM Hook Technology


说明:本文源码涉及的Kernel版本是5.10

1. 背景

2001年之前就有很多安全访问控制模型和框架已经被研究和开发出来,用以增强Linux系统的安全性,例如SELinux,DTE,LIDS等等。这些模型和框架大多以各种不同的内核补丁的形式提供,但是没有一个能够获得统治性的地位进而成为Linux内核标准,使用这些系统需要有编译和定制内核的能力,对于没有内核开发经验的普通用户,获得并使用这些系统是有难度的。在2001年的Linux内核峰会上,美国国家安全局(NSA)介绍了他们关于安全增强Linux(SELinux)的工作,当时Linux内核的创始人Linus Torvalds同意Linux内核确实需要一个通用的安全访问控制框架,但他指出最好是通过可加载内核模块的方法,这样可以支持现存的各种不同的安全访问控制系统。因此Linux安全模块(LSM)应运而生。

LSM是在kernel编译的时候通过配置CONFIG_DEFAULT_SECURITY进行选择的built-in kernel里面,当系统中有多个LSM的时候可以通过kernel命令行security=进行配置。

2. LSM框架

Linus Torvalds对Linux安全模块(LSM)提出了三点要求:

  • 真正的通用,当使用一个不同的安全模型的时候,只需要加载一个不同的内核模块
  • 概念上简单,对Linux内核影响最小,高效
  • 能够支持现存的POSIX.1e capabilities逻辑,作为一个可选的安全模块

2.1 设计目标

  1. 以可加载内核模块的形式实现,不会在安全性方面带来明显的损失也不会带来额外的系统开销
  2. 为了满足大多数现存Linux安全增强系统的需要,采取简化设计的决策减少了对Linux内核的修改和影响

为了满足这些设计目标,Linux安全模块(LSM)采用了通过在内核源代码中放置钩子的方法,来决策是否可以对内核内部对象进行的访问,这些对象有:任务,inode结点,打开的文件等等。LSM访问内核态对象的大致流程如下图所示:

2.2 LSM工作原理

为了更加容易理解LSM的工作原理,现在以Linux打开文件int open(const char *pathname, int flags, mode_t mode);为例来观察kernel中的LSM模块是如何工作。假设用户态进程调用open接口想要打开某个路径下面的文件,那么会有如下的大致流程:

  1. 用户态进程调用以文件路径filepath为入参调用open接口
  2. open系统调用在內核态得到调度,filepath字符串用来帮助找到kernel file object
  3. kernel DAC模块校验文件权限(即用户态进程是否有文件的open权限)
  4. kernel LSM框架依次调用所有激活的LSM模块的file_open相关的勾子函数,只要有一个勾子函数返回错误则中断该系统调用
  5. 所有安全检查通过之后,进程打开文件并返回文件描述符给用户态

2.3 勾子函数

目前kernel LSM(version 5.10)框架总共包括了224个勾子点。简单说明一下比较常见的勾子点:

1. Task Hooks
LSM provides a set of task hooks that enable security modules to manage process security information and to control process operations

2. Program Loading Hooks
LSM provides a set of programloading hooks that are called at critical points during the processing of an execve operation."linux_binprm"

3. IPC Hooks
Security modules can manage security information and perform access control for System V IPC using the LSM IPC hooks.
LSM inserts a hook into the existing ipcperms function so that a security module can perform a check for each existing Linux IPC permission check 

4. Filesystem Hooks
For file operations, three sets of hooks were defined:
    1) filesystem hooks
    2) inode hooks
    3) file hooks

5. Network Hooks
Application layer access to networking is mediated using a set of socket hooks. These hooks, which include interposition of all socket system calls, provide coarse mediation coverage of all socket-based protocols.
Since active user sockets have an associated inode structure, a separate security field was not added to the socket structure or to the lower-level sock structure.
As the socket hooks allow general mediation of network traffic in relation to processes, LSM significantly expands the kernel’s network access control framework (which is already handled at the network layer by Netfilter)(LSM对网络的访问控制和Netfilter保持兼容). 
For example, the sock rcv skb hook allows an inbound packet to be mediated in terms of its destination application, prior to being queued at the associated userspace socket.

6. Other Hooks
LSM provides two additional sets of hooks: 
    1) module hooks
    Module hooks can be used to control the kernel operations that create, initialize, and delete kernel modules.

    2) a set of top-level system hooks
    System hooks can be used to control system operations, such as setting the system hostname, accessing I/O ports, and configuring process accounting.

3. LSM源码分析

3.1. LSM初始化

/**
 * security_init - initializes the security framework
 *
 * This should be called early in the kernel initialization sequence.
 */
int __init security_init(void)
{
    int i;
    struct hlist_head *list = (struct hlist_head *) &security_hook_heads;

    pr_info("Security Framework initializing\n");

    /* 初始化hook list用来保存hook点 */
    for (i = 0; i < sizeof(security_hook_heads) / sizeof(struct hlist_head);
         i  )
        INIT_HLIST_HEAD(&list[i]);

    /**
     * LSM分为major和minor两种,major具备排他性(SELinux,AppArmor,TOMOYO),
     * major LSM都是一种MAC实现,所以通过用户态配置来加载。LSM实现的排他性通过
     * LSM_FLAG_EXCLUSIVE控制。
     */
    /*
     * Load minor LSMs, with the capability module always first.
     */
    capability_add_hooks();
    yama_add_hooks();
    loadpin_add_hooks();

    /* 根据cmdline和builtin配置参数加载具体的LSM模块,例如SELinux */
    /* Load LSMs in specified order. */
    ordered_lsm_init();

    return 0;
}

/* Initialize a given LSM, if it is enabled. */
static void __init initialize_lsm(struct lsm_info *lsm)
{
    if (is_enabled(lsm)) {
        int ret;

        init_debug("initializing %s\n", lsm->name);
        ret = lsm->init();
        WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
    }
}

3.2. LSM模块(以SELinux模块为例)

/* ------------------------------------------------------------- */
/* 以SELinux模块为例看LSM的初始化 */
/* SELinux requires early initialization in order to label
   all processes and objects when they are created. */
DEFINE_LSM(selinux) = {
    .name = "selinux",
    .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
    .enabled = &selinux_enabled,
    .init = selinux_init,
};
static __init int selinux_init(void)
{
    pr_info("SELinux:  Initializing.\n");

    memset(&selinux_state, 0, sizeof(selinux_state));
    enforcing_set(&selinux_state, selinux_enforcing_boot);
    selinux_state.checkreqprot = selinux_checkreqprot_boot;
    selinux_ss_init(&selinux_state.ss);
    selinux_avc_init(&selinux_state.avc);

    /* Set the security state for the initial task. */
    cred_init_security();

    default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC);

    sel_inode_cache = kmem_cache_create("selinux_inode_security",
                        sizeof(struct inode_security_struct),
                        0, SLAB_PANIC, NULL);
    file_security_cache = kmem_cache_create("selinux_file_security",
                        sizeof(struct file_security_struct),
                        0, SLAB_PANIC, NULL);
    avc_init();

    avtab_cache_init();

    ebitmap_cache_init();

    hashtab_cache_init();

    /* 增加SELinux的勾子点 */
    security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux");

    if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
        panic("SELinux: Unable to register AVC netcache callback\n");

    if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET))
        panic("SELinux: Unable to register AVC LSM notifier callback\n");

    if (selinux_enforcing_boot)
        pr_debug("SELinux:  Starting in enforcing mode\n");
    else
        pr_debug("SELinux:  Starting in permissive mode\n");

    return 0;
}
/* SELinux 定义的勾子链表 */
static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
    LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr),
    LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction),
    LSM_HOOK_INIT(binder_transfer_binder, selinux_binder_transfer_binder),
    LSM_HOOK_INIT(binder_transfer_file, selinux_binder_transfer_file),

    LSM_HOOK_INIT(ptrace_access_check, selinux_ptrace_access_check),
    LSM_HOOK_INIT(ptrace_traceme, selinux_ptrace_traceme),
    LSM_HOOK_INIT(capget, selinux_capget),
    LSM_HOOK_INIT(capset, selinux_capset),
    LSM_HOOK_INIT(capable, selinux_capable),
    LSM_HOOK_INIT(quotactl, selinux_quotactl),
    LSM_HOOK_INIT(quota_on, selinux_quota_on),
    LSM_HOOK_INIT(syslog, selinux_syslog),
    LSM_HOOK_INIT(vm_enough_memory, selinux_vm_enough_memory),

    LSM_HOOK_INIT(netlink_send, selinux_netlink_send),

    LSM_HOOK_INIT(bprm_set_creds, selinux_bprm_set_creds),
    LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds),
    LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds),

    LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
    LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
    LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data),
    LSM_HOOK_INIT(sb_remount, selinux_sb_remount),
    LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
    LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
    LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs),
    LSM_HOOK_INIT(sb_mount, selinux_mount),
    LSM_HOOK_INIT(sb_umount, selinux_umount),
    LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts),
    LSM_HOOK_INIT(sb_clone_mnt_opts, selinux_sb_clone_mnt_opts),
    LSM_HOOK_INIT(sb_parse_opts_str, selinux_parse_opts_str),

    LSM_HOOK_INIT(dentry_init_security, selinux_dentry_init_security),
    LSM_HOOK_INIT(dentry_create_files_as, selinux_dentry_create_files_as),

    LSM_HOOK_INIT(inode_alloc_security, selinux_inode_alloc_security),
    LSM_HOOK_INIT(inode_free_security, selinux_inode_free_security),
    LSM_HOOK_INIT(inode_init_security, selinux_inode_init_security),
    LSM_HOOK_INIT(inode_create, selinux_inode_create),
    LSM_HOOK_INIT(inode_link, selinux_inode_link),
    LSM_HOOK_INIT(inode_unlink, selinux_inode_unlink),
    LSM_HOOK_INIT(inode_symlink, selinux_inode_symlink),
    LSM_HOOK_INIT(inode_mkdir, selinux_inode_mkdir),
    LSM_HOOK_INIT(inode_rmdir, selinux_inode_rmdir),
    LSM_HOOK_INIT(inode_mknod, selinux_inode_mknod),
    LSM_HOOK_INIT(inode_rename, selinux_inode_rename),
    LSM_HOOK_INIT(inode_readlink, selinux_inode_readlink),
    LSM_HOOK_INIT(inode_follow_link, selinux_inode_follow_link),
    LSM_HOOK_INIT(inode_permission, selinux_inode_permission),
    LSM_HOOK_INIT(inode_setattr, selinux_inode_setattr),
    LSM_HOOK_INIT(inode_getattr, selinux_inode_getattr),
    LSM_HOOK_INIT(inode_setxattr, selinux_inode_setxattr),
    LSM_HOOK_INIT(inode_post_setxattr, selinux_inode_post_setxattr),
    LSM_HOOK_INIT(inode_getxattr, selinux_inode_getxattr),
    LSM_HOOK_INIT(inode_listxattr, selinux_inode_listxattr),
    LSM_HOOK_INIT(inode_removexattr, selinux_inode_removexattr),
    LSM_HOOK_INIT(inode_getsecurity, selinux_inode_getsecurity),
    LSM_HOOK_INIT(inode_setsecurity, selinux_inode_setsecurity),
    LSM_HOOK_INIT(inode_listsecurity, selinux_inode_listsecurity),
    LSM_HOOK_INIT(inode_getsecid, selinux_inode_getsecid),
    LSM_HOOK_INIT(inode_copy_up, selinux_inode_copy_up),
    LSM_HOOK_INIT(inode_copy_up_xattr, selinux_inode_copy_up_xattr),

    LSM_HOOK_INIT(file_permission, selinux_file_permission),
    LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security),
    LSM_HOOK_INIT(file_free_security, selinux_file_free_security),
    LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl),
    LSM_HOOK_INIT(mmap_file, selinux_mmap_file),
    LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr),
    LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect),
    LSM_HOOK_INIT(file_lock, selinux_file_lock),
    LSM_HOOK_INIT(file_fcntl, selinux_file_fcntl),
    LSM_HOOK_INIT(file_set_fowner, selinux_file_set_fowner),
    LSM_HOOK_INIT(file_send_sigiotask, selinux_file_send_sigiotask),
    LSM_HOOK_INIT(file_receive, selinux_file_receive),

    /**
     * 在LSM框架管理的勾子链表中增加file_open,其中勾子函数为
     * selinux_file_open,即:
     * file_open = head_list{
     *   .head=security_hook_heads,
     *   .hook=head_list{
     *       .head=selinux_file_open,
     *   }
     * };
     */
    LSM_HOOK_INIT(file_open, selinux_file_open),

    LSM_HOOK_INIT(task_alloc, selinux_task_alloc),
    LSM_HOOK_INIT(cred_alloc_blank, selinux_cred_alloc_blank),
    LSM_HOOK_INIT(cred_free, selinux_cred_free),
    LSM_HOOK_INIT(cred_prepare, selinux_cred_prepare),
    LSM_HOOK_INIT(cred_transfer, selinux_cred_transfer),
    LSM_HOOK_INIT(cred_getsecid, selinux_cred_getsecid),
    LSM_HOOK_INIT(kernel_act_as, selinux_kernel_act_as),
    LSM_HOOK_INIT(kernel_create_files_as, selinux_kernel_create_files_as),
    LSM_HOOK_INIT(kernel_module_request, selinux_kernel_module_request),
    LSM_HOOK_INIT(kernel_load_data, selinux_kernel_load_data),
    LSM_HOOK_INIT(kernel_read_file, selinux_kernel_read_file),
    LSM_HOOK_INIT(task_setpgid, selinux_task_setpgid),
    LSM_HOOK_INIT(task_getpgid, selinux_task_getpgid),
    LSM_HOOK_INIT(task_getsid, selinux_task_getsid),
    LSM_HOOK_INIT(task_getsecid, selinux_task_getsecid),
    LSM_HOOK_INIT(task_setnice, selinux_task_setnice),
    LSM_HOOK_INIT(task_setioprio, selinux_task_setioprio),
    LSM_HOOK_INIT(task_getioprio, selinux_task_getioprio),
    LSM_HOOK_INIT(task_prlimit, selinux_task_prlimit),
    LSM_HOOK_INIT(task_setrlimit, selinux_task_setrlimit),
    LSM_HOOK_INIT(task_setscheduler, selinux_task_setscheduler),
    LSM_HOOK_INIT(task_getscheduler, selinux_task_getscheduler),
    LSM_HOOK_INIT(task_movememory, selinux_task_movememory),
    LSM_HOOK_INIT(task_kill, selinux_task_kill),
    LSM_HOOK_INIT(task_to_inode, selinux_task_to_inode),

    LSM_HOOK_INIT(ipc_permission, selinux_ipc_permission),
    LSM_HOOK_INIT(ipc_getsecid, selinux_ipc_getsecid),

    LSM_HOOK_INIT(msg_msg_alloc_security, selinux_msg_msg_alloc_security),
    LSM_HOOK_INIT(msg_msg_free_security, selinux_msg_msg_free_security),

    LSM_HOOK_INIT(msg_queue_alloc_security,
            selinux_msg_queue_alloc_security),
    LSM_HOOK_INIT(msg_queue_free_security, selinux_msg_queue_free_security),
    LSM_HOOK_INIT(msg_queue_associate, selinux_msg_queue_associate),
    LSM_HOOK_INIT(msg_queue_msgctl, selinux_msg_queue_msgctl),
    LSM_HOOK_INIT(msg_queue_msgsnd, selinux_msg_queue_msgsnd),
    LSM_HOOK_INIT(msg_queue_msgrcv, selinux_msg_queue_msgrcv),

    LSM_HOOK_INIT(shm_alloc_security, selinux_shm_alloc_security),
    LSM_HOOK_INIT(shm_free_security, selinux_shm_free_security),
    LSM_HOOK_INIT(shm_associate, selinux_shm_associate),
    LSM_HOOK_INIT(shm_shmctl, selinux_shm_shmctl),
    LSM_HOOK_INIT(shm_shmat, selinux_shm_shmat),

    LSM_HOOK_INIT(sem_alloc_security, selinux_sem_alloc_security),
    LSM_HOOK_INIT(sem_free_security, selinux_sem_free_security),
    LSM_HOOK_INIT(sem_associate, selinux_sem_associate),
    LSM_HOOK_INIT(sem_semctl, selinux_sem_semctl),
    LSM_HOOK_INIT(sem_semop, selinux_sem_semop),

    LSM_HOOK_INIT(d_instantiate, selinux_d_instantiate),

    LSM_HOOK_INIT(getprocattr, selinux_getprocattr),
    LSM_HOOK_INIT(setprocattr, selinux_setprocattr),

    LSM_HOOK_INIT(ismaclabel, selinux_ismaclabel),
    LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx),
    LSM_HOOK_INIT(secctx_to_secid, selinux_secctx_to_secid),
    LSM_HOOK_INIT(release_secctx, selinux_release_secctx),
    LSM_HOOK_INIT(inode_invalidate_secctx, selinux_inode_invalidate_secctx),
    LSM_HOOK_INIT(inode_notifysecctx, selinux_inode_notifysecctx),
    LSM_HOOK_INIT(inode_setsecctx, selinux_inode_setsecctx),
    LSM_HOOK_INIT(inode_getsecctx, selinux_inode_getsecctx),

    LSM_HOOK_INIT(unix_stream_connect, selinux_socket_unix_stream_connect),
    LSM_HOOK_INIT(unix_may_send, selinux_socket_unix_may_send),

    LSM_HOOK_INIT(socket_create, selinux_socket_create),
    LSM_HOOK_INIT(socket_post_create, selinux_socket_post_create),
    LSM_HOOK_INIT(socket_socketpair, selinux_socket_socketpair),
    LSM_HOOK_INIT(socket_bind, selinux_socket_bind),
    LSM_HOOK_INIT(socket_connect, selinux_socket_connect),
    LSM_HOOK_INIT(socket_listen, selinux_socket_listen),
    LSM_HOOK_INIT(socket_accept, selinux_socket_accept),
    LSM_HOOK_INIT(socket_sendmsg, selinux_socket_sendmsg),
    LSM_HOOK_INIT(socket_recvmsg, selinux_socket_recvmsg),
    LSM_HOOK_INIT(socket_getsockname, selinux_socket_getsockname),
    LSM_HOOK_INIT(socket_getpeername, selinux_socket_getpeername),
    LSM_HOOK_INIT(socket_getsockopt, selinux_socket_getsockopt),
    LSM_HOOK_INIT(socket_setsockopt, selinux_socket_setsockopt),
    LSM_HOOK_INIT(socket_shutdown, selinux_socket_shutdown),
    LSM_HOOK_INIT(socket_sock_rcv_skb, selinux_socket_sock_rcv_skb),
    LSM_HOOK_INIT(socket_getpeersec_stream,
            selinux_socket_getpeersec_stream),
    LSM_HOOK_INIT(socket_getpeersec_dgram, selinux_socket_getpeersec_dgram),
    LSM_HOOK_INIT(sk_alloc_security, selinux_sk_alloc_security),
    LSM_HOOK_INIT(sk_free_security, selinux_sk_free_security),
    LSM_HOOK_INIT(sk_clone_security, selinux_sk_clone_security),
    LSM_HOOK_INIT(sk_getsecid, selinux_sk_getsecid),
    LSM_HOOK_INIT(sock_graft, selinux_sock_graft),
    LSM_HOOK_INIT(sctp_assoc_request, selinux_sctp_assoc_request),
    LSM_HOOK_INIT(sctp_sk_clone, selinux_sctp_sk_clone),
    LSM_HOOK_INIT(sctp_bind_connect, selinux_sctp_bind_connect),
    LSM_HOOK_INIT(inet_conn_request, selinux_inet_conn_request),
    LSM_HOOK_INIT(inet_csk_clone, selinux_inet_csk_clone),
    LSM_HOOK_INIT(inet_conn_established, selinux_inet_conn_established),
    LSM_HOOK_INIT(secmark_relabel_packet, selinux_secmark_relabel_packet),
    LSM_HOOK_INIT(secmark_refcount_inc, selinux_secmark_refcount_inc),
    LSM_HOOK_INIT(secmark_refcount_dec, selinux_secmark_refcount_dec),
    LSM_HOOK_INIT(req_classify_flow, selinux_req_classify_flow),
    LSM_HOOK_INIT(tun_dev_alloc_security, selinux_tun_dev_alloc_security),
    LSM_HOOK_INIT(tun_dev_free_security, selinux_tun_dev_free_security),
    LSM_HOOK_INIT(tun_dev_create, selinux_tun_dev_create),
    LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue),
    LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach),
    LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open),
#ifdef CONFIG_SECURITY_INFINIBAND
    LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access),
    LSM_HOOK_INIT(ib_endport_manage_subnet,
              selinux_ib_endport_manage_subnet),
    LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security),
    LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security),
#endif
#ifdef CONFIG_SECURITY_NETWORK_XFRM
    LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc),
    LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone),
    LSM_HOOK_INIT(xfrm_policy_free_security, selinux_xfrm_policy_free),
    LSM_HOOK_INIT(xfrm_policy_delete_security, selinux_xfrm_policy_delete),
    LSM_HOOK_INIT(xfrm_state_alloc, selinux_xfrm_state_alloc),
    LSM_HOOK_INIT(xfrm_state_alloc_acquire,
            selinux_xfrm_state_alloc_acquire),
    LSM_HOOK_INIT(xfrm_state_free_security, selinux_xfrm_state_free),
    LSM_HOOK_INIT(xfrm_state_delete_security, selinux_xfrm_state_delete),
    LSM_HOOK_INIT(xfrm_policy_lookup, selinux_xfrm_policy_lookup),
    LSM_HOOK_INIT(xfrm_state_pol_flow_match,
            selinux_xfrm_state_pol_flow_match),
    LSM_HOOK_INIT(xfrm_decode_session, selinux_xfrm_decode_session),
#endif

#ifdef CONFIG_KEYS
    LSM_HOOK_INIT(key_alloc, selinux_key_alloc),
    LSM_HOOK_INIT(key_free, selinux_key_free),
    LSM_HOOK_INIT(key_permission, selinux_key_permission),
    LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity),
#endif

#ifdef CONFIG_AUDIT
    LSM_HOOK_INIT(audit_rule_init, selinux_audit_rule_init),
    LSM_HOOK_INIT(audit_rule_known, selinux_audit_rule_known),
    LSM_HOOK_INIT(audit_rule_match, selinux_audit_rule_match),
    LSM_HOOK_INIT(audit_rule_free, selinux_audit_rule_free),
#endif

#ifdef CONFIG_BPF_SYSCALL
    LSM_HOOK_INIT(bpf, selinux_bpf),
    LSM_HOOK_INIT(bpf_map, selinux_bpf_map),
    LSM_HOOK_INIT(bpf_prog, selinux_bpf_prog),
    LSM_HOOK_INIT(bpf_map_alloc_security, selinux_bpf_map_alloc),
    LSM_HOOK_INIT(bpf_prog_alloc_security, selinux_bpf_prog_alloc),
    LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free),
    LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free),
#endif
};

3.3. LSM模块生效(以SELinux和open为例)

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
    return ksys_open(filename, flags, mode);
}

static inline long ksys_open(const char __user *filename, int flags,
                 umode_t mode)
{
    if (force_o_largefile())
        flags |= O_LARGEFILE;
    return do_sys_open(AT_FDCWD, filename, flags, mode);
}

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    struct open_how how = build_open_how(flags, mode);
    return do_sys_openat2(dfd, filename, &how);
}

...

struct file *do_filp_open(int dfd, struct filename *pathname,
        const struct open_flags *op);

static struct file *path_openat(struct nameidata *nd,
            const struct open_flags *op, unsigned flags);
            
static int do_o_path(struct nameidata *nd, unsigned flags, struct file *file);

int vfs_open(const struct path *path, struct file *file);
...

int vfs_open(const struct path *path, struct file *file)
{
    file->f_path = *path;
    return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
}
static int do_dentry_open(struct file *f,
              struct inode *inode,
              int (*open)(struct inode *, struct file *))
{
    ...
    
    error = security_file_open(f);
    if (error)
        goto cleanup_all;

cleanup_all:
    if (WARN_ON_ONCE(error > 0))
        error = -EINVAL;
    fops_put(f->f_op);
    if (f->f_mode & FMODE_WRITER) {
        put_write_access(inode);
        __mnt_drop_write(f->f_path.mnt);
    }
cleanup_file:
    path_put(&f->f_path);
    f->f_path.mnt = NULL;
    f->f_path.dentry = NULL;
    f->f_inode = NULL;
    return error;
}
int security_file_open(struct file *file)
{
    int ret;

    ret = call_int_hook(file_open, 0, file);
    if (ret)
        return ret;

    return fsnotify_perm(file, MAY_OPEN);
}
#define call_int_hook(FUNC, IRC, ...) ({            \
    int RC = IRC;                       \
    do {                            \
        struct security_hook_list *P;           \
                                \
        hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
            RC = P->hook.FUNC(__VA_ARGS__);     \
            if (RC != 0)                \
                break;              \
        }                       \
    } while (0);                        \
    RC;                         \
})
static int selinux_file_open(struct file *file)
{
    struct file_security_struct *fsec;
    struct inode_security_struct *isec;

    fsec = file->f_security;
    isec = inode_security(file_inode(file));
    /*
     * Save inode label and policy sequence number
     * at open-time so that selinux_file_permission
     * can determine whether revalidation is necessary.
     * Task label is already saved in the file security
     * struct as its SID.
     */
    fsec->isid = isec->sid;
    fsec->pseqno = avc_policy_seqno(&selinux_state);
    /*
     * Since the inode label or policy seqno may have changed
     * between the selinux_inode_permission check and the saving
     * of state above, recheck that access is still permitted.
     * Otherwise, access might never be revalidated against the
     * new inode label or new policy.
     * This check is not redundant - do not remove.
     */
    return file_path_has_perm(file->f_cred, file, open_file_to_av(file));
}

4 KRSI LSM模块源码分析

TODO

5 LSM模块编程

实现一个获取当前执行进程的绝对路径的LSM模块,代码如下:

test_lsm_module.c

#include <linux/security.h>
#include <linux/sysctl.h>
#include <linux/ptrace.h>
#include <linux/prctl.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include <linux/path.h>
#include <linux/kprobes.h>
#include <linux/module.h> 

static int (*register_security_p)(struct security_operations *ops);
static int (*unregister_security_p)(struct security_operations * ops);

// do nothing
int kprobe_pre(struct kprobe *p, struct pt_regs *regs)
{
        return 0;
}

static void* acquire_func_by_kprobe(char* func_name)
{
    void *symbol_addr=NULL;
    struct kprobe kp;

    do
    {
       memset(&kp, 0, sizeof(kp));
       kp.symbol_name = func_name;
       kp.pre_handler = kprobe_pre;
       if(register_kprobe(&kp) != 0)
       {
            symbol_addr=(void*)kp.addr;
            break;
       }
       //this is the address of  "symbol_name"
       symbol_addr = (void*)kp.addr;

       //now kprobe is not used any more,so unregister it
       unregister_kprobe(&kp);

    }while(false);

    return symbol_addr;
}

int execve_lsm_hook(struct linux_binprm *bprm)
{
    struct cred *currentCred;

    if (bprm->filename)
    {
        printk("file: %s\n", bprm->filename);
        //printk("file argument: %s\n",bprm->p);
    }
    else
    {
        printk("file exec\n");
    } 
    
    currentCred = current->cred;    
    printk(KERN_INFO "uid = %d\n", currentCred->uid);
    printk(KERN_INFO "gid = %d\n", currentCred->gid);
    printk(KERN_INFO "suid = %d\n", currentCred->suid);
    printk(KERN_INFO "sgid = %d\n", currentCred->sgid);
    printk(KERN_INFO "euid = %d\n", currentCred->euid);
    printk(KERN_INFO "egid = %d\n", currentCred->egid);  

    printk("comm: %s\n", current->comm);

    return 0;
} 

static struct security_operations test_security_ops = 
{
        .name = "test",
        .bprm_check_security = execve_lsm_hook,
};

static __init int test_init(void)
{
    int ret_val = -1;

    //获取register_security的导出地址
    register_security_p = acquire_func_by_kprobe("register_security");
    printk("register_security:%p\n", register_security_p);   
    if (!register_security_p)
    {
        printk("get register_security error\n");
    }  

    //获取unregister_security的导出地址
    unregister_security_p = acquire_func_by_kprobe("unregister_security");
    printk("unregister_security:%p\n", unregister_security_p);
    if (!unregister_security_p)
    {
        printk("get unregister_security error\n"); 
    }
     
    //注册LSMsbas
     //if (register_security_p && unregister_security_p)
     if (register_security_p)
    {
        ret_val = register_security_p(&test_security_ops); 
        printk("register_security:%d\n", ret_val);  
    }  

       return 0;
} 

static void test_cleanup(void)
{
    int ret_val=-1;

    //if (register_security_p && unregister_security_p)
    if (unregister_security_p)
    {
        ret_val = register_security_p(&test_security_ops); 
        printk("register_security:%d\n", ret_val);  
    }

    return;
} 

module_init(test_init);
module_exit(test_cleanup);

//一定要有这个声明,否则会有unknown module symbol error
MODULE_LICENSE("GPL");

Makefile

obj-m := execve_lsm_hook.o
PWD       := $(shell pwd)

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    rm -rf *.o *~ core .*.cmd *.mod.c ./tmp_version *.ko modules.order  Module.symvers

clean_omit:
    rm -rf *.o *~ core .*.cmd *.mod.c ./tmp_version modules.order  Module.symvers

随记_2021-05-18


随记_2021–05–18

饭后畅想退休生活,感觉开个咖啡馆挺不错的,像这样的:

下班路上看到了一个酒吧,感觉开一个也挺不错的,像这样:

然后倒一杯咖啡或者精酿一坐一天,为什么能坐一天呢?一来不差钱,一来年纪大了,毕竟上了年纪的人目之所及皆是回忆,心之所想皆是过往;眼之所看皆是遗憾,耳之所闻皆是呢喃!轻呷一口,厚重的嗓音随后响起:

“辛丑年,钱塘江”

“互联网江湖”

“乱”

“很乱”

“非常乱”

……

有点古龙的味道,也有点期待退休的日子,按照国家规定算来算去我还有40+年才能这么做,哎~~

随记_2021-04-05


清明假期最后一天陪家人来了一次清明一日游,早起给儿子去法喜寺求福,然后去老头儿吃了午饭,吃完以后去城市阳台转了转,回家小憩一会之后带着儿子去游泳课。用老婆的话来说“痛并快乐的一天”,晚上虽然累,还是想把这一天零零碎碎的想法记录下来。

  • 原来法喜寺又叫做法喜讲寺,也叫上天竺,在越国就有了
  • 今天看到了法喜寺有一个很大的牌匾,大概的意思是宗教山之首,我感慨了一下,貌似有点冒犯神明
  • 今天在法喜寺参拜的时候,虽然去之初是有目的的,但是当我闭上眼睛参拜的时候,一时间忘记自己应该怎么跟佛祖对话表达自己的愿望,不知道是心诚还是不诚。参拜的时候在想佛祖的存在虚无缥缈,众生所求是什么呢?心安、名利、抑或是寄托,我愿意相信有佛祖这样的存在,只是芸芸众生之一的我大概率是不会遇到这样的存在,所以我所求是什么?在参拜的时候,我在想应该会有拖着病体前来祈求的人,可是病痛真的会少吗?或者心理觉得好过一点,其实并没有真正的变好
  • 城市阳台的游玩充分释放了儿子的活力,难得的亲子时间,家人脸上散开的笑容让我感受到了家的温馨,脑海中突然冒出了一段话”个人性格和经历造成了我自身存在的一些问题,不善沟通、敏感偏执,但是幸运的是家人让我学会了爱和被爱“
  • 剩下的,好像老婆对自己的身材有非常大的不满,我打算找找减肥计划打印出俩给她,她很懒估计肯定不会去打印一个什么减肥计划,至于减肥计划的坚持实施只能留到后面再想办法

P.S. 我很好奇为什么老婆的膀胱如此强大,在外面玩的时候我去洗手间的频率比她高得多,难道是肾虚。。。

谈谈最近看到的一本书


最近看了一本书,我觉得是一本很神奇的书,书名《鹅鹅鹅》,作者叫二冬。这本书大概是长下面这样的:

刚开始读这本书的时候,有点平淡如水的感觉,就好像刚吃了一顿美味的“麻辣烫”之后再喝粥,这其中味道的寡淡不言而喻。由于是通勤的时候随手拿起来的,没有很强的目的性来读这本书,不知不觉也就这么看下去了,就像喝水一样就这么喝着喝着。两天之后回过头来看发现自己随着作者带入到他书中描述的生活中了,大鹅、摩托车、蜱虫、狗、山。没有大道理,有一些生活感悟也是蜻蜓点水般带过。之所以说这本书神奇恰恰就是这里,我居然就这么沉浸其中了,愿意看读下去不觉得乏味,细细想来应该是这本书的字里行间充斥生活气息,少了微信、头条这类媒体上泛滥的焦虑感。

我开始对作者很好奇,不过还没有时间去对作者做一些了解,不过我敢肯定,他的文风是我喜欢的,如果我坚持写作的话,我希望自己也可以这样。

Golang中的SOLID原则


SOLID原则是2002年提出的,关于这个原则的描述和应用大多都是用Java代码作为示例,对于在SOLID原则之后十多年才被创造出来的Golang跟Java这类古老的语言还是有明显的差异的,所以有必要看看如何基于SOLID原则设计Golang的程序。

单一职责原则

A class should have one, and only one, reason to change.

单一职责最大的好处是最小的代码量达成修改目标,提到最小修改代码量就是涉及到耦合和内聚问题。Go里面没有class的概念,在Go中所有的代码都在一个package中,通过import使两个package建立源码级别的耦合,因而从粗粒度上来看Go的单一职责发生在package,当然从函数和方法的角度也会涉及到单一职责问题,这个问题是各种语言都会碰到的。Go标准库中的一些优秀 package 示例:

  • net/http – 提供 http 客户端和服务端
  • os/exec – 执行外部命令
  • encoding/json – 实现 JSON 文档的编码和解码

开放/封闭原则

Software entities should be open for extension, but closed for modification.

如下面的代码所示,我们有一个go类型A ,有一个字段year和一个方法Greet。我们有第二种类型B,它嵌入了一个A,因为A嵌入,因此调用者看到 B 的方法覆盖了A的方法。因为A作为字段嵌入B ,B可以提供自己的Greet方法,掩盖了A的Greet方法。但嵌入不仅适用于方法,还可以访问嵌入类型的字段。如您所见,因为A和B都在同一个包中定义,所以B可以访问A的私有year字段,就像在B中声明一样。因此嵌入是一个强大的工具,允许go的类型对扩展开放。

package main

type A struct {
        year int
}

func (a A) Greet() { fmt.Println("Hello Golang", a.year) }

type B struct {
        A
}

func (b B) Greet() { fmt.Println("Welcome to Golang", b.year) }

func main() {
        var a A
        a.year = 2021
        var b B
        b.year = 2021
        a.Greet() // Hello Golang 2021
        b.Greet() // Welcome to Golang 2021
}

里氏替换原则

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

在基于类的语言中,里氏替换原则通常被解释为,具有各种具体子类型的抽象基类的规范,但是go没有类和继承,所以没有办法根据抽象类层次结构实现替换,go中需要实现替换的是接口(即interface),这一点深刻影响了go的变成范式:go的类型(struct)不需要指定它们实现特定接口(interface)而是任何类型都可以实现接口,只要它具有的函数签名与接口声明的方法匹配即可

如下代码所示,crypto.randos.File都实现了io.Reader接口,分别操作/dev/random设备和磁盘文件:

// io.Reader
type Reader interface {
        // Read reads up to len(buf) bytes into buf.
        Read(buf []byte) (n int, err error)
}

// crypto.rand.Reader
type devReader struct {
    name string
    f    io.Reader
    mu   sync.Mutex
    used int32 // atomic; whether this devReader has been used
}
func (r *devReader) Read(b []byte) (n int, err error) {
    if atomic.CompareAndSwapInt32(&r.used, 0, 1) {
        // First use of randomness. Start timer to warn about
        // being blocked on entropy not being available.
        t := time.AfterFunc(60*time.Second, warnBlocked)
        defer t.Stop()
    }
    if altGetRandom != nil && r.name == urandomDevice && altGetRandom(b) {
        return len(b), nil
    }
    r.mu.Lock()
    defer r.mu.Unlock()
    if r.f == nil {
        f, err := os.Open(r.name)
        if f == nil {
            return 0, err
        }
        if runtime.GOOS == "plan9" {
            r.f = f
        } else {
            r.f = bufio.NewReader(hideAgainReader{f})
        }
    }
    return r.f.Read(b)
}

// os.File
type File struct {
    *file // os specific
}
func (f *File) Read(b []byte) (n int, err error) {
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    return n, f.wrapErr("read", e)
}

接口隔离原则

Clients should not be forced to depend on methods they do not use.

接口隔离原则简单来说就是建立单一的接口, 不要建立臃肿庞大的接口。也就是接口尽量细化,同时接口中的方法尽量少,保持接口纯洁性。go的标准库io里面定义了大量的interface,每个interface的函数都不多,由于interface都比较小,所以他们能够方便的互相组合,比如ReadWriter是有Reader和Writer组成的。

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Closer is the interface that wraps the basic Close method.
//
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
    Close() error
}

// Seeker is the interface that wraps the basic Seek method.
//
// Seek sets the offset for the next Read or Write to offset,
// interpreted according to whence:
// SeekStart means relative to the start of the file,
// SeekCurrent means relative to the current offset, and
// SeekEnd means relative to the end.
// Seek returns the new offset relative to the start of the
// file and an error, if any.
//
// Seeking to an offset before the start of the file is an error.
// Seeking to any positive offset is legal, but the behavior of subsequent
// I/O operations on the underlying object is implementation-dependent.
type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
    Reader
    Writer
}

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
    Reader
    Closer
}

// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
    Writer
    Closer
}

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// ReadSeeker is the interface that groups the basic Read and Seek methods.
type ReadSeeker interface {
    Reader
    Seeker
}

// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
    Writer
    Seeker
}

// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
type ReadWriteSeeker interface {
    Reader
    Writer
    Seeker
}

依赖倒置原则

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

依赖调换

依赖倒置其实就是变换依赖方和被依赖方的位置:

  • 高层模块不应该依赖底层模块,都应该依赖于抽象
  • 抽象不依赖具体实现
  • 具体实现依赖抽象

如下图所示,对象A依赖对象B,经过依赖倒置之后对象A依赖变成了抽象A(interfaceA),然后对象B实现了抽象A。

package A

type B_Abstract interface {
    GetB() int
}

type A struct {
    Va int
}

func (a *A) Add(b B_Abstract) int {
    return b.GetB() + a.a
}
package B

type B struct {
    Vb int
}

func(b *B) GetB() int {
    return b.b
}
package main

import (
    "A"
    "B"
)

func main() {
    a := A.A{Va:1}
    b := B.B{Vb:1}
    a.Add(b)
}

依赖管理

如果go的项目已经遵守了前面的4个原则,那么代码应该已经被分解到不同的package中了,每个package都有一个明确定义的责任或目的。项目中代码应该根据接口描述其依赖关系,并且应该考虑这些接口以仅描述这些函数所需的行为。除此之外,还需要注意项目的依赖关系即项目的import graph结构,在go中import graph必须是非循环的。不遵守这种非循环要求将导致编译失败,但更为严重地是它代表设计中存在严重错误。

在所有条件相同的情况下,精心设计的go程序的import graph应该是宽的,相对平坦的,而不是高而窄的。 如果你有一个package,其函数无法在不借助另一个package的情况下运行,那么这或许表明代码没有很好地沿pakcage边界分解。

依赖倒置原则鼓励将特定的责任,沿着import graph尽可能的推向更高层级,推给main package或顶级处理程序,留下较低级别的代码来处理抽象接口。

总结来讲,如果按照SOLID原则来安排go的项目,cycle import等问题可以迎刃而解。