// SPDX-License-Identifier: GPL-2.0-only
/*
* Minimal file system backend for holding eBPF maps and programs,
* used by bpf(2) object pinning.
*
* Authors:
*
* Daniel Borkmann <daniel@iogearbox.net>
*/
#include <linux/init.h>
#include <linux/magic.h>
#include <linux/major.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/kdev_t.h>
#include <linux/filter.h>
#include <linux/bpf.h>
#include <linux/bpf_trace.h>
#include "preload/bpf_preload.h"
enum bpf_type {
BPF_TYPE_UNSPEC = 0,
BPF_TYPE_PROG,
BPF_TYPE_MAP,
BPF_TYPE_LINK,
};
static void *bpf_any_get(void *raw, enum bpf_type type)
{
switch (type) {
case BPF_TYPE_PROG:
bpf_prog_inc(raw);
break;
case BPF_TYPE_MAP:
bpf_map_inc_with_uref(raw);
break;
case BPF_TYPE_LINK:
bpf_link_inc(raw);
break;
default:
WARN_ON_ONCE(1);
break;
}
return raw;
}
static void bpf_any_put(void *raw, enum bpf_type type)
{
switch (type) {
case BPF_TYPE_PROG:
bpf_prog_put(raw);
break;
case BPF_TYPE_MAP:
bpf_map_put_with_uref(raw);
break;
case BPF_TYPE_LINK:
bpf_link_put(raw);
break;
default:
WARN_ON_ONCE(1);
break;
}
}
static void *bpf_fd_probe_obj(u32 ufd, enum bpf_type *type)
{
void *raw;
raw = bpf_map_get_with_uref(ufd);
if (!IS_ERR(raw)) {
*type = BPF_TYPE_MAP;
return raw;
}
raw = bpf_prog_get(ufd);
if (!IS_ERR(raw)) {
*type = BPF_TYPE_PROG;
return raw;
}
raw = bpf_link_get_from_fd(ufd);
if (!IS_ERR(raw)) {
*type = BPF_TYPE_LINK;
return raw;
}
return ERR_PTR(-EINVAL);
}
static const struct inode_operations bpf_dir_iops;
static const struct inode_operations bpf_prog_iops = { };
static const struct inode_operations bpf_map_iops = { };
static const struct inode_operations bpf_link_iops = { };
static struct inode *bpf_get_inode(struct super_block *sb,
const struct inode *dir,
umode_t mode)
{
struct inode *inode;
switch (mode & S_IFMT) {
case S_IFDIR:
case S_IFREG:
case S_IFLNK:
break;
default:
return ERR_PTR(-EINVAL);
}
inode = new_inode(sb);
if (!inode)
return ERR_PTR(-ENOSPC);
inode->i_ino = get_next_ino();
inode->i_atime = current_time(inode);
inode->i_mtime = inode->i_atime;
inode->i_ctime = inode->i_atime;
inode_init_owner(inode, dir, mode);
return inode;
}
static int bpf_inode_type(const struct inode *inode, enum bpf_type *type)
{
*type = BPF_TYPE_UNSPEC;
if (inode->i_op == &bpf_prog_iops)
*type = BPF_TYPE_PROG;
else if (inode->i_op == &bpf_map_iops)
*type = BPF_TYPE_MAP;
else if (inode->i_op == &bpf_link_iops)
*type = BPF_TYPE_LINK;
else
return -EACCES;
return 0;
}
static void bpf_dentry_finalize(struct dentry *dentry, struct inode *inode,
struct inode *dir)
{
d_instantiate(dentry, inode);
dget(dentry);
dir->i_mtime = current_time(dir);
dir->i_ctime = dir->i_mtime;
}
static int bpf_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
struct inode *inode;
inode = bpf_get_inode(dir->i_sb, dir, mode | S_IFDIR);
if (IS_ERR(inode))
return PTR_ERR(inode);
inode->i_op = &bpf_dir_iops;
inode->i_fop = &simple_dir_operations;
inc_nlink(inode);
inc_nlink(dir);
bpf_dentry_finalize(dentry, inode, dir);
return 0;
}
struct map_iter {
void *key;
bool done;
};
static struct map_iter *map_iter(struct seq_file *m)
{
return m->private;
}
static struct bpf_map *seq_file_to_map(struct seq_file *m)
{
return file_inode(m->file)->i_private;
}
static void map_iter_free(struct map_iter *iter)
{
if (iter) {
kfree(iter->key);
kfree(iter);
}
}
static struct map_iter *map_iter_alloc(struct bpf_map *map)
{
struct map_iter *iter;
iter = kzalloc(sizeof(*iter), GFP_KERNEL | __GFP_NOWARN);
if (!iter)
goto error;
iter->key = kzalloc(map->key_size, GFP_KERNEL | __GFP_NOWARN