root/drivers/android/binder/rust_binderfs.c
// SPDX-License-Identifier: GPL-2.0

#include <linux/compiler_types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/gfp.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/ipc_namespace.h>
#include <linux/kdev_t.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/namei.h>
#include <linux/magic.h>
#include <linux/major.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/mount.h>
#include <linux/fs_parser.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock_types.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/user_namespace.h>
#include <linux/xarray.h>
#include <uapi/asm-generic/errno-base.h>
#include <uapi/linux/android/binder.h>
#include <uapi/linux/android/binderfs.h>

#include "rust_binder.h"
#include "rust_binder_internal.h"

#define FIRST_INODE 1
#define SECOND_INODE 2
#define INODE_OFFSET 3
#define BINDERFS_MAX_MINOR (1U << MINORBITS)
/* Ensure that the initial ipc namespace always has devices available. */
#define BINDERFS_MAX_MINOR_CAPPED (BINDERFS_MAX_MINOR - 4)

DEFINE_SHOW_ATTRIBUTE(rust_binder_stats);
DEFINE_SHOW_ATTRIBUTE(rust_binder_state);
DEFINE_SHOW_ATTRIBUTE(rust_binder_transactions);
DEFINE_SHOW_ATTRIBUTE(rust_binder_proc);

char *rust_binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;
module_param_named(rust_devices, rust_binder_devices_param, charp, 0444);

static dev_t binderfs_dev;
static DEFINE_MUTEX(binderfs_minors_mutex);
static DEFINE_IDA(binderfs_minors);

enum binderfs_param {
        Opt_max,
        Opt_stats_mode,
};

enum binderfs_stats_mode {
        binderfs_stats_mode_unset,
        binderfs_stats_mode_global,
};

struct binder_features {
        bool oneway_spam_detection;
        bool extended_error;
        bool freeze_notification;
};

static const struct constant_table binderfs_param_stats[] = {
        { "global", binderfs_stats_mode_global },
        {}
};

static const struct fs_parameter_spec binderfs_fs_parameters[] = {
        fsparam_u32("max",      Opt_max),
        fsparam_enum("stats",   Opt_stats_mode, binderfs_param_stats),
        {}
};

static struct binder_features binder_features = {
        .oneway_spam_detection = true,
        .extended_error = true,
        .freeze_notification = true,
};

static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb)
{
        return sb->s_fs_info;
}

/**
 * binderfs_binder_device_create - allocate inode from super block of a
 *                                 binderfs mount
 * @ref_inode: inode from wich the super block will be taken
 * @userp:     buffer to copy information about new device for userspace to
 * @req:       struct binderfs_device as copied from userspace
 *
 * This function allocates a new binder_device and reserves a new minor
 * number for it.
 * Minor numbers are limited and tracked globally in binderfs_minors. The
 * function will stash a struct binder_device for the specific binder
 * device in i_private of the inode.
 * It will go on to allocate a new inode from the super block of the
 * filesystem mount, stash a struct binder_device in its i_private field
 * and attach a dentry to that inode.
 *
 * Return: 0 on success, negative errno on failure
 */
static int binderfs_binder_device_create(struct inode *ref_inode,
                                         struct binderfs_device __user *userp,
                                         struct binderfs_device *req)
{
        int minor, ret;
        struct dentry *dentry, *root;
        struct binder_device *device = NULL;
        rust_binder_context ctx = NULL;
        struct inode *inode = NULL;
        struct super_block *sb = ref_inode->i_sb;
        struct binderfs_info *info = sb->s_fs_info;
#if defined(CONFIG_IPC_NS)
        bool use_reserve = (info->ipc_ns == &init_ipc_ns);
#else
        bool use_reserve = true;
#endif

        /* Reserve new minor number for the new device. */
        mutex_lock(&binderfs_minors_mutex);
        if (++info->device_count <= info->mount_opts.max)
                minor = ida_alloc_max(&binderfs_minors,
                                      use_reserve ? BINDERFS_MAX_MINOR - 1 :
                                                    BINDERFS_MAX_MINOR_CAPPED - 1,
                                      GFP_KERNEL);
        else
                minor = -ENOSPC;
        if (minor < 0) {
                --info->device_count;
                mutex_unlock(&binderfs_minors_mutex);
                return minor;
        }
        mutex_unlock(&binderfs_minors_mutex);

        ret = -ENOMEM;
        device = kzalloc_obj(*device);
        if (!device)
                goto err;

        req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */

        ctx = rust_binder_new_context(req->name);
        if (!ctx)
                goto err;

        inode = new_inode(sb);
        if (!inode)
                goto err;

        inode->i_ino = minor + INODE_OFFSET;
        simple_inode_init_ts(inode);
        init_special_inode(inode, S_IFCHR | 0600,
                           MKDEV(MAJOR(binderfs_dev), minor));
        inode->i_fop = &rust_binder_fops;
        inode->i_uid = info->root_uid;
        inode->i_gid = info->root_gid;

        req->major = MAJOR(binderfs_dev);
        req->minor = minor;
        device->ctx = ctx;
        device->minor = minor;

        if (userp && copy_to_user(userp, req, sizeof(*req))) {
                ret = -EFAULT;
                goto err;
        }

        root = sb->s_root;
        dentry = simple_start_creating(root, req->name);
        if (IS_ERR(dentry)) {
                ret = PTR_ERR(dentry);
                goto err;
        }

        inode->i_private = device;
        d_make_persistent(dentry, inode);

        fsnotify_create(root->d_inode, dentry);
        simple_done_creating(dentry);

        return 0;

err:
        kfree(device);
        rust_binder_remove_context(ctx);
        mutex_lock(&binderfs_minors_mutex);
        --info->device_count;
        ida_free(&binderfs_minors, minor);
        mutex_unlock(&binderfs_minors_mutex);
        iput(inode);

        return ret;
}

/**
 * binder_ctl_ioctl - handle binder device node allocation requests
 *
 * The request handler for the binder-control device. All requests operate on
 * the binderfs mount the binder-control device resides in:
 * - BINDER_CTL_ADD
 *   Allocate a new binder device.
 *
 * Return: %0 on success, negative errno on failure.
 */
static long binder_ctl_ioctl(struct file *file, unsigned int cmd,
                             unsigned long arg)
{
        int ret = -EINVAL;
        struct inode *inode = file_inode(file);
        struct binderfs_device __user *device = (struct binderfs_device __user *)arg;
        struct binderfs_device device_req;

        switch (cmd) {
        case BINDER_CTL_ADD:
                ret = copy_from_user(&device_req, device, sizeof(device_req));
                if (ret) {
                        ret = -EFAULT;
                        break;
                }

                ret = binderfs_binder_device_create(inode, device, &device_req);
                break;
        default:
                break;
        }

        return ret;
}

static void binderfs_evict_inode(struct inode *inode)
{
        struct binder_device *device = inode->i_private;
        struct binderfs_info *info = BINDERFS_SB(inode->i_sb);

        clear_inode(inode);

        if (!S_ISCHR(inode->i_mode) || !device)
                return;

        mutex_lock(&binderfs_minors_mutex);
        --info->device_count;
        ida_free(&binderfs_minors, device->minor);
        mutex_unlock(&binderfs_minors_mutex);

        /* ctx is null for binder-control, but this function ignores null pointers */
        rust_binder_remove_context(device->ctx);

        kfree(device);
}

static int binderfs_fs_context_parse_param(struct fs_context *fc,
                                           struct fs_parameter *param)
{
        int opt;
        struct binderfs_mount_opts *ctx = fc->fs_private;
        struct fs_parse_result result;

        opt = fs_parse(fc, binderfs_fs_parameters, param, &result);
        if (opt < 0)
                return opt;

        switch (opt) {
        case Opt_max:
                if (result.uint_32 > BINDERFS_MAX_MINOR)
                        return invalfc(fc, "Bad value for '%s'", param->key);

                ctx->max = result.uint_32;
                break;
        case Opt_stats_mode:
                if (!capable(CAP_SYS_ADMIN))
                        return -EPERM;

                ctx->stats_mode = result.uint_32;
                break;
        default:
                return invalfc(fc, "Unsupported parameter '%s'", param->key);
        }

        return 0;
}

static int binderfs_fs_context_reconfigure(struct fs_context *fc)
{
        struct binderfs_mount_opts *ctx = fc->fs_private;
        struct binderfs_info *info = BINDERFS_SB(fc->root->d_sb);

        if (info->mount_opts.stats_mode != ctx->stats_mode)
                return invalfc(fc, "Binderfs stats mode cannot be changed during a remount");

        info->mount_opts.stats_mode = ctx->stats_mode;
        info->mount_opts.max = ctx->max;
        return 0;
}

static int binderfs_show_options(struct seq_file *seq, struct dentry *root)
{
        struct binderfs_info *info = BINDERFS_SB(root->d_sb);

        if (info->mount_opts.max <= BINDERFS_MAX_MINOR)
                seq_printf(seq, ",max=%d", info->mount_opts.max);

        switch (info->mount_opts.stats_mode) {
        case binderfs_stats_mode_unset:
                break;
        case binderfs_stats_mode_global:
                seq_puts(seq, ",stats=global");
                break;
        }

        return 0;
}

static const struct super_operations binderfs_super_ops = {
        .evict_inode    = binderfs_evict_inode,
        .show_options   = binderfs_show_options,
        .statfs         = simple_statfs,
};

static inline bool is_binderfs_control_device(const struct dentry *dentry)
{
        struct binderfs_info *info = dentry->d_sb->s_fs_info;

        return info->control_dentry == dentry;
}

static int binderfs_rename(struct mnt_idmap *idmap,
                           struct inode *old_dir, struct dentry *old_dentry,
                           struct inode *new_dir, struct dentry *new_dentry,
                           unsigned int flags)
{
        if (is_binderfs_control_device(old_dentry) ||
            is_binderfs_control_device(new_dentry))
                return -EPERM;

        return simple_rename(idmap, old_dir, old_dentry, new_dir,
                             new_dentry, flags);
}

static int binderfs_unlink(struct inode *dir, struct dentry *dentry)
{
        if (is_binderfs_control_device(dentry))
                return -EPERM;

        return simple_unlink(dir, dentry);
}

static const struct file_operations binder_ctl_fops = {
        .owner          = THIS_MODULE,
        .open           = nonseekable_open,
        .unlocked_ioctl = binder_ctl_ioctl,
        .compat_ioctl   = binder_ctl_ioctl,
        .llseek         = noop_llseek,
};

/**
 * binderfs_binder_ctl_create - create a new binder-control device
 * @sb: super block of the binderfs mount
 *
 * This function creates a new binder-control device node in the binderfs mount
 * referred to by @sb.
 *
 * Return: 0 on success, negative errno on failure
 */
static int binderfs_binder_ctl_create(struct super_block *sb)
{
        int minor, ret;
        struct dentry *dentry;
        struct binder_device *device;
        struct inode *inode = NULL;
        struct dentry *root = sb->s_root;
        struct binderfs_info *info = sb->s_fs_info;
#if defined(CONFIG_IPC_NS)
        bool use_reserve = (info->ipc_ns == &init_ipc_ns);
#else
        bool use_reserve = true;
#endif

        device = kzalloc_obj(*device);
        if (!device)
                return -ENOMEM;

        ret = -ENOMEM;
        inode = new_inode(sb);
        if (!inode)
                goto out;

        /* Reserve a new minor number for the new device. */
        mutex_lock(&binderfs_minors_mutex);
        minor = ida_alloc_max(&binderfs_minors,
                              use_reserve ? BINDERFS_MAX_MINOR - 1 :
                                            BINDERFS_MAX_MINOR_CAPPED - 1,
                              GFP_KERNEL);
        mutex_unlock(&binderfs_minors_mutex);
        if (minor < 0) {
                ret = minor;
                goto out;
        }

        inode->i_ino = SECOND_INODE;
        simple_inode_init_ts(inode);
        init_special_inode(inode, S_IFCHR | 0600,
                           MKDEV(MAJOR(binderfs_dev), minor));
        inode->i_fop = &binder_ctl_fops;
        inode->i_uid = info->root_uid;
        inode->i_gid = info->root_gid;

        device->minor = minor;
        device->ctx = NULL;

        dentry = d_alloc_name(root, "binder-control");
        if (!dentry)
                goto out;

        inode->i_private = device;
        info->control_dentry = dentry;
        d_make_persistent(dentry, inode);
        dput(dentry);

        return 0;

out:
        kfree(device);
        iput(inode);

        return ret;
}

static const struct inode_operations binderfs_dir_inode_operations = {
        .lookup = simple_lookup,
        .rename = binderfs_rename,
        .unlink = binderfs_unlink,
};

static struct inode *binderfs_make_inode(struct super_block *sb, int mode)
{
        struct inode *ret;

        ret = new_inode(sb);
        if (ret) {
                ret->i_ino = iunique(sb, BINDERFS_MAX_MINOR + INODE_OFFSET);
                ret->i_mode = mode;
                simple_inode_init_ts(ret);
        }
        return ret;
}

void rust_binderfs_remove_file(struct dentry *dentry)
{
        simple_recursive_removal(dentry, NULL);
}

static struct dentry *rust_binderfs_create_file(struct dentry *parent, const char *name,
                                                const struct file_operations *fops,
                                                void *data)
{
        struct dentry *dentry;
        struct inode *new_inode;

        new_inode = binderfs_make_inode(parent->d_sb, S_IFREG | 0444);
        if (!new_inode)
                return ERR_PTR(-ENOMEM);
        new_inode->i_fop = fops;
        new_inode->i_private = data;

        dentry = simple_start_creating(parent, name);
        if (IS_ERR(dentry)) {
                iput(new_inode);
                return dentry;
        }

        d_make_persistent(dentry, new_inode);
        fsnotify_create(parent->d_inode, dentry);
        simple_done_creating(dentry);
        return dentry;
}

struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid)
{
        struct binderfs_info *info = nodp->i_sb->s_fs_info;
        struct dentry *dir = info->proc_log_dir;
        char strbuf[20 + 1];
        void *data = (void *)(unsigned long) pid;

        if (!dir)
                return NULL;

        snprintf(strbuf, sizeof(strbuf), "%u", pid);
        return rust_binderfs_create_file(dir, strbuf, &rust_binder_proc_fops, data);
}

static struct dentry *binderfs_create_dir(struct dentry *parent,
                                          const char *name)
{
        struct dentry *dentry;
        struct inode *new_inode;

        new_inode = binderfs_make_inode(parent->d_sb, S_IFDIR | 0755);
        if (!new_inode)
                return ERR_PTR(-ENOMEM);

        new_inode->i_fop = &simple_dir_operations;
        new_inode->i_op = &simple_dir_inode_operations;

        dentry = simple_start_creating(parent, name);
        if (IS_ERR(dentry)) {
                iput(new_inode);
                return dentry;
        }

        inc_nlink(parent->d_inode);
        set_nlink(new_inode, 2);
        d_make_persistent(dentry, new_inode);
        fsnotify_mkdir(parent->d_inode, dentry);
        simple_done_creating(dentry);
        return dentry;
}

static int binder_features_show(struct seq_file *m, void *unused)
{
        bool *feature = m->private;

        seq_printf(m, "%d\n", *feature);

        return 0;
}
DEFINE_SHOW_ATTRIBUTE(binder_features);

static int init_binder_features(struct super_block *sb)
{
        struct dentry *dentry, *dir;

        dir = binderfs_create_dir(sb->s_root, "features");
        if (IS_ERR(dir))
                return PTR_ERR(dir);

        dentry = rust_binderfs_create_file(dir, "oneway_spam_detection",
                                      &binder_features_fops,
                                      &binder_features.oneway_spam_detection);
        if (IS_ERR(dentry))
                return PTR_ERR(dentry);

        dentry = rust_binderfs_create_file(dir, "extended_error",
                                      &binder_features_fops,
                                      &binder_features.extended_error);
        if (IS_ERR(dentry))
                return PTR_ERR(dentry);

        dentry = rust_binderfs_create_file(dir, "freeze_notification",
                                      &binder_features_fops,
                                      &binder_features.freeze_notification);
        if (IS_ERR(dentry))
                return PTR_ERR(dentry);

        return 0;
}

static int init_binder_logs(struct super_block *sb)
{
        struct dentry *binder_logs_root_dir, *dentry, *proc_log_dir;
        struct binderfs_info *info;
        int ret = 0;

        binder_logs_root_dir = binderfs_create_dir(sb->s_root,
                                                   "binder_logs");
        if (IS_ERR(binder_logs_root_dir)) {
                ret = PTR_ERR(binder_logs_root_dir);
                goto out;
        }

        dentry = rust_binderfs_create_file(binder_logs_root_dir, "stats",
                                      &rust_binder_stats_fops, NULL);
        if (IS_ERR(dentry)) {
                ret = PTR_ERR(dentry);
                goto out;
        }

        dentry = rust_binderfs_create_file(binder_logs_root_dir, "state",
                                      &rust_binder_state_fops, NULL);
        if (IS_ERR(dentry)) {
                ret = PTR_ERR(dentry);
                goto out;
        }

        dentry = rust_binderfs_create_file(binder_logs_root_dir, "transactions",
                                      &rust_binder_transactions_fops, NULL);
        if (IS_ERR(dentry)) {
                ret = PTR_ERR(dentry);
                goto out;
        }

        proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc");
        if (IS_ERR(proc_log_dir)) {
                ret = PTR_ERR(proc_log_dir);
                goto out;
        }
        info = sb->s_fs_info;
        info->proc_log_dir = proc_log_dir;

out:
        return ret;
}

static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
        int ret;
        struct binderfs_info *info;
        struct binderfs_mount_opts *ctx = fc->fs_private;
        struct inode *inode = NULL;
        struct binderfs_device device_info = {};
        const char *name;
        size_t len;

        sb->s_blocksize = PAGE_SIZE;
        sb->s_blocksize_bits = PAGE_SHIFT;

        /*
         * The binderfs filesystem can be mounted by userns root in a
         * non-initial userns. By default such mounts have the SB_I_NODEV flag
         * set in s_iflags to prevent security issues where userns root can
         * just create random device nodes via mknod() since it owns the
         * filesystem mount. But binderfs does not allow to create any files
         * including devices nodes. The only way to create binder devices nodes
         * is through the binder-control device which userns root is explicitly
         * allowed to do. So removing the SB_I_NODEV flag from s_iflags is both
         * necessary and safe.
         */
        sb->s_iflags &= ~SB_I_NODEV;
        sb->s_iflags |= SB_I_NOEXEC;
        sb->s_magic = RUST_BINDERFS_SUPER_MAGIC;
        sb->s_op = &binderfs_super_ops;
        sb->s_time_gran = 1;

        sb->s_fs_info = kzalloc_obj(struct binderfs_info);
        if (!sb->s_fs_info)
                return -ENOMEM;
        info = sb->s_fs_info;

        info->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns);

        info->root_gid = make_kgid(sb->s_user_ns, 0);
        if (!gid_valid(info->root_gid))
                info->root_gid = GLOBAL_ROOT_GID;
        info->root_uid = make_kuid(sb->s_user_ns, 0);
        if (!uid_valid(info->root_uid))
                info->root_uid = GLOBAL_ROOT_UID;
        info->mount_opts.max = ctx->max;
        info->mount_opts.stats_mode = ctx->stats_mode;

        inode = new_inode(sb);
        if (!inode)
                return -ENOMEM;

        inode->i_ino = FIRST_INODE;
        inode->i_fop = &simple_dir_operations;
        inode->i_mode = S_IFDIR | 0755;
        simple_inode_init_ts(inode);
        inode->i_op = &binderfs_dir_inode_operations;
        set_nlink(inode, 2);

        sb->s_root = d_make_root(inode);
        if (!sb->s_root)
                return -ENOMEM;

        ret = binderfs_binder_ctl_create(sb);
        if (ret)
                return ret;

        name = rust_binder_devices_param;
        for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
                strscpy(device_info.name, name, len + 1);
                ret = binderfs_binder_device_create(inode, NULL, &device_info);
                if (ret)
                        return ret;
                name += len;
                if (*name == ',')
                        name++;
        }

        ret = init_binder_features(sb);
        if (ret)
                return ret;

        if (info->mount_opts.stats_mode == binderfs_stats_mode_global)
                return init_binder_logs(sb);

        return 0;
}

static int binderfs_fs_context_get_tree(struct fs_context *fc)
{
        return get_tree_nodev(fc, binderfs_fill_super);
}

static void binderfs_fs_context_free(struct fs_context *fc)
{
        struct binderfs_mount_opts *ctx = fc->fs_private;

        kfree(ctx);
}

static const struct fs_context_operations binderfs_fs_context_ops = {
        .free           = binderfs_fs_context_free,
        .get_tree       = binderfs_fs_context_get_tree,
        .parse_param    = binderfs_fs_context_parse_param,
        .reconfigure    = binderfs_fs_context_reconfigure,
};

static int binderfs_init_fs_context(struct fs_context *fc)
{
        struct binderfs_mount_opts *ctx;

        ctx = kzalloc_obj(struct binderfs_mount_opts);
        if (!ctx)
                return -ENOMEM;

        ctx->max = BINDERFS_MAX_MINOR;
        ctx->stats_mode = binderfs_stats_mode_unset;

        fc->fs_private = ctx;
        fc->ops = &binderfs_fs_context_ops;

        return 0;
}

static void binderfs_kill_super(struct super_block *sb)
{
        struct binderfs_info *info = sb->s_fs_info;

        /*
         * During inode eviction struct binderfs_info is needed.
         * So first wipe the super_block then free struct binderfs_info.
         */
        kill_anon_super(sb);

        if (info && info->ipc_ns)
                put_ipc_ns(info->ipc_ns);

        kfree(info);
}

static struct file_system_type binder_fs_type = {
        .name                   = "binder",
        .init_fs_context        = binderfs_init_fs_context,
        .parameters             = binderfs_fs_parameters,
        .kill_sb                = binderfs_kill_super,
        .fs_flags               = FS_USERNS_MOUNT,
};

int init_rust_binderfs(void)
{
        int ret;
        const char *name;
        size_t len;

        /* Verify that the default binderfs device names are valid. */
        name = rust_binder_devices_param;
        for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
                if (len > BINDERFS_MAX_NAME)
                        return -E2BIG;
                name += len;
                if (*name == ',')
                        name++;
        }

        /* Allocate new major number for binderfs. */
        ret = alloc_chrdev_region(&binderfs_dev, 0, BINDERFS_MAX_MINOR,
                                  "rust_binder");
        if (ret)
                return ret;

        ret = register_filesystem(&binder_fs_type);
        if (ret) {
                unregister_chrdev_region(binderfs_dev, BINDERFS_MAX_MINOR);
                return ret;
        }

        return ret;
}