| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Container Security Monitor module | |
| * | |
| * Copyright (c) 2018 Google, Inc | |
| */ | |
| #include "monitor.h" | |
| #include "process.h" | |
| #include <linux/audit.h> | |
| #include <linux/file.h> | |
| #include <linux/lsm_hooks.h> | |
| #include <linux/module.h> | |
| #include <linux/pipe_fs_i.h> | |
| #include <linux/poll.h> | |
| #include <linux/rwsem.h> | |
| #include <linux/seq_file.h> | |
| #include <linux/string.h> | |
| #include <linux/sysctl.h> | |
| #include <overlayfs/overlayfs.h> | |
| #include <uapi/linux/magic.h> | |
| /* protects csm_*_enabled and configurations. */ | |
| DECLARE_RWSEM(csm_rwsem_config); | |
| /* queue used for poll wait on config changes. */ | |
| static DECLARE_WAIT_QUEUE_HEAD(config_wait); | |
| /* increase each time a new configuration is applied. */ | |
| static unsigned long config_version; | |
| /* Stats gathered from the LSM. */ | |
| struct container_stats csm_stats; | |
| struct container_stats_mapping { | |
| const char *key; | |
| size_t *value; | |
| }; | |
| /* Key value pair mapping for the sysfs entry. */ | |
| struct container_stats_mapping csm_stats_mapping[] = { | |
| { "ProtoEncodingFailed", &csm_stats.proto_encoding_failed }, | |
| { "WorkQueueFailed", &csm_stats.workqueue_failed }, | |
| { "EventWritingFailed", &csm_stats.event_writing_failed }, | |
| { "SizePickingFailed", &csm_stats.size_picking_failed }, | |
| { "PipeAlreadyOpened", &csm_stats.pipe_already_opened }, | |
| { "CsmSetxattr", &csm_stats.csm_setxattr }, | |
| }; | |
| /* | |
| * Is monitoring enabled? Defaults to disabled. | |
| * These variables might be used without locking csm_rwsem_config to check if an | |
| * LSM hook can bail quickly. The semaphore is taken later to ensure CSM is | |
| * still enabled. | |
| * | |
| * csm_enabled is true if any collector is enabled. | |
| */ | |
| bool csm_enabled; | |
| static bool csm_container_enabled; | |
| bool csm_execute_enabled; | |
| bool csm_memexec_enabled; | |
| /* securityfs control files */ | |
| static struct dentry *csm_dir; | |
| static struct dentry *csm_enabled_file; | |
| static struct dentry *csm_container_file; | |
| static struct dentry *csm_config_file; | |
| static struct dentry *csm_config_vers_file; | |
| static struct dentry *csm_pipe_file; | |
| static struct dentry *csm_stats_file; | |
| /* Pipes to forward data to user-mode. */ | |
| DECLARE_RWSEM(csm_rwsem_pipe); | |
| static struct file *csm_user_read_pipe; | |
| struct file *csm_user_write_pipe; | |
| /* Option to disable the CSM features at boot. */ | |
| static bool cmdline_boot_disabled; | |
| bool cmdline_boot_vsock_enabled; | |
| /* Options disabled by default. */ | |
| static bool cmdline_boot_pipe_enabled; | |
| static bool cmdline_boot_config_enabled; | |
| /* Option to fully enabled the LSM at boot for automated testing. */ | |
| static bool cmdline_default_enabled; | |
| static int csm_boot_disabled_setup(char *str) | |
| { | |
| return kstrtobool(str, &cmdline_boot_disabled); | |
| } | |
| early_param("csm.disabled", csm_boot_disabled_setup); | |
| static int csm_default_enabled_setup(char *str) | |
| { | |
| return kstrtobool(str, &cmdline_default_enabled); | |
| } | |
| early_param("csm.default.enabled", csm_default_enabled_setup); | |
| static int csm_boot_vsock_enabled_setup(char *str) | |
| { | |
| return kstrtobool(str, &cmdline_boot_vsock_enabled); | |
| } | |
| early_param("csm.vsock.enabled", csm_boot_vsock_enabled_setup); | |
| static int csm_boot_pipe_enabled_setup(char *str) | |
| { | |
| return kstrtobool(str, &cmdline_boot_pipe_enabled); | |
| } | |
| early_param("csm.pipe.enabled", csm_boot_pipe_enabled_setup); | |
| static int csm_boot_config_enabled_setup(char *str) | |
| { | |
| return kstrtobool(str, &cmdline_boot_config_enabled); | |
| } | |
| early_param("csm.config.enabled", csm_boot_config_enabled_setup); | |
| static bool pipe_in_use(void) | |
| { | |
| struct pipe_inode_info *pipe; | |
| lockdep_assert_held_write(&csm_rwsem_config); | |
| if (csm_user_read_pipe) { | |
| pipe = get_pipe_info(csm_user_read_pipe, false); | |
| if (pipe) | |
| return READ_ONCE(pipe->readers) > 1; | |
| } | |
| return false; | |
| } | |
| /* Close pipe, force has to be true to close pipe if it is still being used. */ | |
| int close_pipe_files(bool force) | |
| { | |
| if (csm_user_read_pipe) { | |
| /* Pipe is still used. */ | |
| if (pipe_in_use()) { | |
| if (!force) | |
| return -EBUSY; | |
| pr_warn("pipe is closed while it is still being used.\n"); | |
| } | |
| fput(csm_user_read_pipe); | |
| fput(csm_user_write_pipe); | |
| csm_user_read_pipe = NULL; | |
| csm_user_write_pipe = NULL; | |
| } | |
| return 0; | |
| } | |
| static void csm_update_config(schema_ConfigurationRequest *req) | |
| { | |
| schema_ExecuteCollectorConfig *econf; | |
| size_t i; | |
| bool enumerate_processes = false; | |
| /* Expect the lock to be held for write before this call. */ | |
| lockdep_assert_held_write(&csm_rwsem_config); | |
| /* This covers the scenario where a client is connected and the config | |
| * transitions the execute collector from disabled to enabled. In that | |
| * case there may have been execute events not sent. So they are | |
| * enumerated. | |
| */ | |
| if (!csm_execute_enabled && req->execute_config.enabled && | |
| pipe_in_use()) | |
| enumerate_processes = true; | |
| csm_container_enabled = req->container_config.enabled; | |
| csm_execute_enabled = req->execute_config.enabled; | |
| csm_memexec_enabled = req->memexec_config.enabled; | |
| /* csm_enabled is true if any collector is enabled. */ | |
| csm_enabled = csm_container_enabled || csm_execute_enabled || | |
| csm_memexec_enabled; | |
| /* Clean-up existing configurations. */ | |
| kfree(csm_execute_config.envp_allowlist); | |
| memset(&csm_execute_config, 0, sizeof(csm_execute_config)); | |
| if (csm_execute_enabled) { | |
| econf = &req->execute_config; | |
| csm_execute_config.argv_limit = econf->argv_limit; | |
| csm_execute_config.envp_limit = econf->envp_limit; | |
| /* Swap the allowlist so it is not freed on return. */ | |
| csm_execute_config.envp_allowlist = econf->envp_allowlist.arg; | |
| econf->envp_allowlist.arg = NULL; | |
| } | |
| /* Reset all stats and close pipe if disabled. */ | |
| if (!csm_enabled) { | |
| for (i = 0; i < ARRAY_SIZE(csm_stats_mapping); i++) | |
| *csm_stats_mapping[i].value = 0; | |
| close_pipe_files(true); | |
| } | |
| config_version++; | |
| if (enumerate_processes) | |
| csm_enumerate_processes(); | |
| wake_up(&config_wait); | |
| } | |
| int csm_update_config_from_buffer(void *data, size_t size) | |
| { | |
| schema_ConfigurationRequest c = {}; | |
| pb_istream_t istream; | |
| c.execute_config.envp_allowlist.funcs.decode = pb_decode_string_array; | |
| istream = pb_istream_from_buffer(data, size); | |
| if (!pb_decode(&istream, schema_ConfigurationRequest_fields, &c)) { | |
| kfree(c.execute_config.envp_allowlist.arg); | |
| return -EINVAL; | |
| } | |
| down_write(&csm_rwsem_config); | |
| csm_update_config(&c); | |
| up_write(&csm_rwsem_config); | |
| return 0; | |
| } | |
| static ssize_t csm_config_write(struct file *file, const char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| ssize_t err = 0; | |
| void *mem; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EPERM; | |
| /* No partial writes. */ | |
| if (*ppos != 0) | |
| return -EINVAL; | |
| /* Duplicate user memory to safely parse protobuf. */ | |
| mem = memdup_user(buf, count); | |
| if (IS_ERR(mem)) | |
| return PTR_ERR(mem); | |
| err = csm_update_config_from_buffer(mem, count); | |
| if (!err) | |
| err = count; | |
| kfree(mem); | |
| return err; | |
| } | |
| static const struct file_operations csm_config_fops = { | |
| .write = csm_config_write, | |
| }; | |
| static void csm_enable(void) | |
| { | |
| schema_ConfigurationRequest req = {}; | |
| /* Expect the lock to be held for write before this call. */ | |
| lockdep_assert_held_write(&csm_rwsem_config); | |
| /* Default configuration */ | |
| req.container_config.enabled = true; | |
| req.execute_config.enabled = true; | |
| req.execute_config.argv_limit = UINT_MAX; | |
| req.execute_config.envp_limit = UINT_MAX; | |
| req.memexec_config.enabled = true; | |
| csm_update_config(&req); | |
| } | |
| static void csm_disable(void) | |
| { | |
| schema_ConfigurationRequest req = {}; | |
| /* Expect the lock to be held for write before this call. */ | |
| lockdep_assert_held_write(&csm_rwsem_config); | |
| /* Zero configuration disable all collectors. */ | |
| csm_update_config(&req); | |
| pr_info("disabled\n"); | |
| } | |
| static ssize_t csm_enabled_read(struct file *file, char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| const char *str = csm_enabled ? "1\n" : "0\n"; | |
| return simple_read_from_buffer(buf, count, ppos, str, 2); | |
| } | |
| static ssize_t csm_enabled_write(struct file *file, const char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| bool enabled; | |
| int err; | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EPERM; | |
| if (count <= 0 || count > PAGE_SIZE || *ppos) | |
| return -EINVAL; | |
| err = kstrtobool_from_user(buf, count, &enabled); | |
| if (err) | |
| return err; | |
| down_write(&csm_rwsem_config); | |
| if (enabled) | |
| csm_enable(); | |
| else | |
| csm_disable(); | |
| up_write(&csm_rwsem_config); | |
| return count; | |
| } | |
| static const struct file_operations csm_enabled_fops = { | |
| .read = csm_enabled_read, | |
| .write = csm_enabled_write, | |
| }; | |
| static int csm_config_version_open(struct inode *inode, struct file *file) | |
| { | |
| /* private_data is used to keep the latest config version read. */ | |
| file->private_data = (void*)-1; | |
| return 0; | |
| } | |
| static ssize_t csm_config_version_read(struct file *file, char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| unsigned long version = config_version; | |
| file->private_data = (void*)version; | |
| return simple_read_from_buffer(buf, count, ppos, &version, | |
| sizeof(version)); | |
| } | |
| static __poll_t csm_config_version_poll(struct file *file, | |
| struct poll_table_struct *poll_tab) | |
| { | |
| if ((unsigned long)file->private_data != config_version) | |
| return EPOLLIN; | |
| poll_wait(file, &config_wait, poll_tab); | |
| if ((unsigned long)file->private_data != config_version) | |
| return EPOLLIN; | |
| return 0; | |
| } | |
| static const struct file_operations csm_config_version_fops = { | |
| .open = csm_config_version_open, | |
| .read = csm_config_version_read, | |
| .poll = csm_config_version_poll, | |
| }; | |
| static int csm_pipe_open(struct inode *inode, struct file *file) | |
| { | |
| if (!capable(CAP_SYS_ADMIN)) | |
| return -EPERM; | |
| if (!csm_enabled) | |
| return -EAGAIN; | |
| return 0; | |
| } | |
| /* Similar to file_clone_open that is available only in 4.19 and up. */ | |
| static inline struct file *pipe_clone_open(struct file *file) | |
| { | |
| return dentry_open(&file->f_path, file->f_flags, file->f_cred); | |
| } | |
| /* Check if the pipe is still used, else recreate and dup it. */ | |
| static struct file *csm_dup_pipe(void) | |
| { | |
| long pipe_size = 1024 * PAGE_SIZE; | |
| long actual_size; | |
| struct file *pipes[2] = {NULL, NULL}; | |
| struct file *ret; | |
| int err; | |
| down_write(&csm_rwsem_pipe); | |
| err = close_pipe_files(false); | |
| if (err) { | |
| ret = ERR_PTR(err); | |
| csm_stats.pipe_already_opened++; | |
| goto out; | |
| } | |
| err = create_pipe_files(pipes, O_NONBLOCK); | |
| if (err) { | |
| ret = ERR_PTR(err); | |
| goto out; | |
| } | |
| /* | |
| * Try to increase the pipe size to 1024 pages, if there is not | |
| * enough memory, pipes will stay unchanged. | |
| */ | |
| actual_size = pipe_fcntl(pipes[0], F_SETPIPE_SZ, pipe_size); | |
| if (actual_size != pipe_size) | |
| pr_err("failed to resize pipe to 1024 pages, error: %ld, fallback to the default value\n", | |
| actual_size); | |
| csm_user_read_pipe = pipes[0]; | |
| csm_user_write_pipe = pipes[1]; | |
| /* Clone the file so we can track if the reader is still used. */ | |
| ret = pipe_clone_open(csm_user_read_pipe); | |
| out: | |
| up_write(&csm_rwsem_pipe); | |
| return ret; | |
| } | |
| static ssize_t csm_pipe_read(struct file *file, char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| int fd; | |
| ssize_t err; | |
| struct file *local_pipe; | |
| /* No partial reads. */ | |
| if (*ppos != 0) | |
| return -EINVAL; | |
| fd = get_unused_fd_flags(0); | |
| if (fd < 0) | |
| return fd; | |
| local_pipe = csm_dup_pipe(); | |
| if (IS_ERR(local_pipe)) { | |
| err = PTR_ERR(local_pipe); | |
| local_pipe = NULL; | |
| goto error; | |
| } | |
| err = simple_read_from_buffer(buf, count, ppos, &fd, sizeof(fd)); | |
| if (err < 0) | |
| goto error; | |
| if (err < sizeof(fd)) { | |
| err = -EINVAL; | |
| goto error; | |
| } | |
| /* Install the file descriptor when we know everything succeeded. */ | |
| fd_install(fd, local_pipe); | |
| csm_enumerate_processes(); | |
| return err; | |
| error: | |
| if (local_pipe) | |
| fput(local_pipe); | |
| put_unused_fd(fd); | |
| return err; | |
| } | |
| static const struct file_operations csm_pipe_fops = { | |
| .open = csm_pipe_open, | |
| .read = csm_pipe_read, | |
| }; | |
| static void set_container_decode_callbacks(schema_Container *container) | |
| { | |
| container->pod_namespace.funcs.decode = pb_decode_string_field; | |
| container->pod_name.funcs.decode = pb_decode_string_field; | |
| container->container_name.funcs.decode = pb_decode_string_field; | |
| container->container_image_uri.funcs.decode = pb_decode_string_field; | |
| container->labels.funcs.decode = pb_decode_string_array; | |
| } | |
| static void set_container_encode_callbacks(schema_Container *container) | |
| { | |
| container->pod_namespace.funcs.encode = pb_encode_string_field; | |
| container->pod_name.funcs.encode = pb_encode_string_field; | |
| container->container_name.funcs.encode = pb_encode_string_field; | |
| container->container_image_uri.funcs.encode = pb_encode_string_field; | |
| container->labels.funcs.encode = pb_encode_string_array; | |
| } | |
| static void free_container_callbacks_args(schema_Container *container) | |
| { | |
| kfree(container->pod_namespace.arg); | |
| kfree(container->pod_name.arg); | |
| kfree(container->container_name.arg); | |
| kfree(container->container_image_uri.arg); | |
| kfree(container->labels.arg); | |
| } | |
| static ssize_t csm_container_write(struct file *file, const char __user *buf, | |
| size_t count, loff_t *ppos) | |
| { | |
| ssize_t err = 0; | |
| void *mem; | |
| u64 cid; | |
| pb_istream_t istream; | |
| struct task_struct *task; | |
| schema_ContainerReport report = {}; | |
| schema_Event event = {}; | |
| schema_Container *container; | |
| char *uuid = NULL; | |
| /* Notify that this collector is not yet enabled. */ | |
| if (!csm_container_enabled) | |
| return -EAGAIN; | |
| /* No partial writes. */ | |
| if (*ppos != 0) | |
| return -EINVAL; | |
| /* Duplicate user memory to safely parse protobuf. */ | |
| mem = memdup_user(buf, count); | |
| if (IS_ERR(mem)) | |
| return PTR_ERR(mem); | |
| /* Callback to decode string in protobuf. */ | |
| set_container_decode_callbacks(&report.container); | |
| istream = pb_istream_from_buffer(mem, count); | |
| if (!pb_decode(&istream, schema_ContainerReport_fields, &report)) { | |
| err = -EINVAL; | |
| goto out; | |
| } | |
| /* Check protobuf is as expected */ | |
| if (report.pid == 0 || | |
| report.container.container_id != 0) { | |
| err = -EINVAL; | |
| goto out; | |
| } | |
| /* Find if the process id is linked to an existing container-id. */ | |
| rcu_read_lock(); | |
| task = find_task_by_pid_ns(report.pid, &init_pid_ns); | |
| if (task) { | |
| cid = audit_get_contid(task); | |
| if (cid == AUDIT_CID_UNSET) | |
| err = -ENOENT; | |
| } else { | |
| err = -ENOENT; | |
| } | |
| rcu_read_unlock(); | |
| if (err) | |
| goto out; | |
| uuid = kzalloc(PROCESS_UUID_SIZE, GFP_KERNEL); | |
| if (!uuid) | |
| goto out; | |
| /* Provide the uuid for the top process of the container. */ | |
| err = get_process_uuid_by_pid(report.pid, uuid, PROCESS_UUID_SIZE); | |
| if (err) | |
| goto out; | |
| /* Correct the container-id and feed the event to pipe */ | |
| report.has_container = true; | |
| report.container.container_id = cid; | |
| report.container.init_uuid.funcs.encode = pb_encode_uuid_field; | |
| report.container.init_uuid.arg = uuid; | |
| container = &event.event.container.container; | |
| *container = report.container; | |
| /* Use encode callback to generate the final proto. */ | |
| set_container_encode_callbacks(container); | |
| event.which_event = schema_Event_container_tag; | |
| event.event.container.has_container = true; | |
| err = csm_sendeventproto(schema_Event_fields, &event); | |
| if (!err) | |
| err = count; | |
| out: | |
| /* Free any allocated nanopb callback arguments. */ | |
| free_container_callbacks_args(&report.container); | |
| kfree(uuid); | |
| kfree(mem); | |
| return err; | |
| } | |
| static const struct file_operations csm_container_fops = { | |
| .write = csm_container_write, | |
| }; | |
| static int csm_show_stats(struct seq_file *p, void *v) | |
| { | |
| size_t i; | |
| for (i = 0; i < ARRAY_SIZE(csm_stats_mapping); i++) { | |
| seq_printf(p, "%s:\t%zu\n", | |
| csm_stats_mapping[i].key, | |
| *csm_stats_mapping[i].value); | |
| } | |
| return 0; | |
| } | |
| static int csm_stats_open(struct inode *inode, struct file *file) | |
| { | |
| size_t i, size = 1; /* Start at one for the null byte. */ | |
| for (i = 0; i < ARRAY_SIZE(csm_stats_mapping); i++) { | |
| /* | |
| * Calculate the maximum length: | |
| * - Length of the key | |
| * - 3 additional chars :\t\n | |
| * - longest unsigned 64-bit integer. | |
| */ | |
| size += strlen(csm_stats_mapping[i].key) | |
| + 3 + sizeof("18446744073709551615"); | |
| } | |
| return single_open_size(file, csm_show_stats, NULL, size); | |
| } | |
| static const struct file_operations csm_stats_fops = { | |
| .open = csm_stats_open, | |
| .read = seq_read, | |
| .llseek = seq_lseek, | |
| .release = single_release, | |
| }; | |
| static bool is_d_overlayfs_mounted(struct dentry *dentry) | |
| { | |
| struct super_block *mnt_sb; | |
| if (dentry == NULL || dentry->d_inode == NULL) | |
| return false; | |
| mnt_sb = dentry->d_inode->i_sb; | |
| if (mnt_sb == NULL || mnt_sb->s_magic != OVERLAYFS_SUPER_MAGIC) | |
| return false; | |
| return true; | |
| } | |
| static int csm_setxattr(struct dentry *dentry, const char *name, | |
| const void *value, size_t size, int flags) | |
| { | |
| if (csm_enabled && | |
| (audit_get_contid(current) != AUDIT_CID_UNSET) && | |
| is_d_overlayfs_mounted(dentry) && | |
| (strcmp(name, XATTR_SECURITY_CSM) == 0)) | |
| csm_stats.csm_setxattr++; | |
| return 0; | |
| } | |
| static struct security_hook_list csm_hooks[] __lsm_ro_after_init = { | |
| /* Track process execution. */ | |
| LSM_HOOK_INIT(bprm_check_security, csm_bprm_check_security), | |
| LSM_HOOK_INIT(task_post_alloc, csm_task_post_alloc), | |
| LSM_HOOK_INIT(task_exit, csm_task_exit), | |
| /* Track memory execution */ | |
| LSM_HOOK_INIT(file_mprotect, csm_mprotect), | |
| LSM_HOOK_INIT(mmap_file, csm_mmap_file), | |
| /* Track file modification provenance. */ | |
| LSM_HOOK_INIT(file_pre_free_security, csm_file_pre_free), | |
| /* Block modyfing csm xattr. */ | |
| LSM_HOOK_INIT(inode_setxattr, csm_setxattr), | |
| }; | |
| static int __init csm_init(void) | |
| { | |
| int err; | |
| if (cmdline_boot_disabled) | |
| return 0; | |
| if (cmdline_boot_vsock_enabled) | |
| pr_debug("vsock is deprecated, but was enabled at boot\n"); | |
| csm_dir = securityfs_create_dir("container_monitor", NULL); | |
| if (IS_ERR(csm_dir)) { | |
| err = PTR_ERR(csm_dir); | |
| goto error; | |
| } | |
| csm_enabled_file = securityfs_create_file("enabled", 0644, csm_dir, | |
| NULL, &csm_enabled_fops); | |
| if (IS_ERR(csm_enabled_file)) { | |
| err = PTR_ERR(csm_enabled_file); | |
| goto error_rmdir; | |
| } | |
| csm_container_file = securityfs_create_file("container", 0200, csm_dir, | |
| NULL, &csm_container_fops); | |
| if (IS_ERR(csm_container_file)) { | |
| err = PTR_ERR(csm_container_file); | |
| goto error_rm_enabled; | |
| } | |
| csm_config_vers_file = securityfs_create_file("config_version", 0400, | |
| csm_dir, NULL, | |
| &csm_config_version_fops); | |
| if (IS_ERR(csm_config_vers_file)) { | |
| err = PTR_ERR(csm_config_vers_file); | |
| goto error_rm_container; | |
| } | |
| if (cmdline_boot_config_enabled) { | |
| csm_config_file = securityfs_create_file("config", 0200, | |
| csm_dir, NULL, | |
| &csm_config_fops); | |
| if (IS_ERR(csm_config_file)) { | |
| err = PTR_ERR(csm_config_file); | |
| goto error_rm_config_vers; | |
| } | |
| } | |
| if (cmdline_boot_pipe_enabled) { | |
| csm_pipe_file = securityfs_create_file("pipe", 0400, csm_dir, | |
| NULL, &csm_pipe_fops); | |
| if (IS_ERR(csm_pipe_file)) { | |
| err = PTR_ERR(csm_pipe_file); | |
| goto error_rm_config; | |
| } | |
| } | |
| csm_stats_file = securityfs_create_file("stats", 0400, csm_dir, | |
| NULL, &csm_stats_fops); | |
| if (IS_ERR(csm_stats_file)) { | |
| err = PTR_ERR(csm_stats_file); | |
| goto error_rm_pipe; | |
| } | |
| pr_debug("created securityfs control files\n"); | |
| security_add_hooks(csm_hooks, ARRAY_SIZE(csm_hooks), "csm"); | |
| pr_debug("registered hooks\n"); | |
| /* Off-by-default, only used for testing images. */ | |
| if (cmdline_default_enabled) { | |
| down_write(&csm_rwsem_config); | |
| csm_enable(); | |
| up_write(&csm_rwsem_config); | |
| } | |
| return 0; | |
| error_rm_pipe: | |
| if (cmdline_boot_pipe_enabled) | |
| securityfs_remove(csm_pipe_file); | |
| error_rm_config: | |
| if (cmdline_boot_config_enabled) | |
| securityfs_remove(csm_config_file); | |
| error_rm_config_vers: | |
| securityfs_remove(csm_config_vers_file); | |
| error_rm_container: | |
| securityfs_remove(csm_container_file); | |
| error_rm_enabled: | |
| securityfs_remove(csm_enabled_file); | |
| error_rmdir: | |
| securityfs_remove(csm_dir); | |
| error: | |
| pr_warn("fs initialization error: %d", err); | |
| return err; | |
| } | |
| late_initcall(csm_init); |