xref: /linux/fs/fuse/backing.c (revision c51248524a0f546b9a9b44710038f5663688ed10)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * FUSE passthrough to backing file.
4  *
5  * Copyright (c) 2023 CTERA Networks.
6  */
7 
8 #include "dev.h"
9 #include "fuse_i.h"
10 
11 #include <linux/file.h>
12 
13 struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
14 {
15 	if (fb && refcount_inc_not_zero(&fb->count))
16 		return fb;
17 	return NULL;
18 }
19 
20 static void fuse_backing_free(struct fuse_backing *fb)
21 {
22 	pr_debug("%s: fb=0x%p\n", __func__, fb);
23 
24 	if (fb->file)
25 		fput(fb->file);
26 	put_cred(fb->cred);
27 	kfree_rcu(fb, rcu);
28 }
29 
30 void fuse_backing_put(struct fuse_backing *fb)
31 {
32 	if (fb && refcount_dec_and_test(&fb->count))
33 		fuse_backing_free(fb);
34 }
35 
36 void fuse_backing_files_init(struct fuse_conn *fc)
37 {
38 	idr_init(&fc->backing_files_map);
39 }
40 
41 static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb)
42 {
43 	int id;
44 
45 	idr_preload(GFP_KERNEL);
46 	spin_lock(&fc->lock);
47 	/* FIXME: xarray might be space inefficient */
48 	id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC);
49 	spin_unlock(&fc->lock);
50 	idr_preload_end();
51 
52 	WARN_ON_ONCE(id == 0);
53 	return id;
54 }
55 
56 static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc,
57 						   int id)
58 {
59 	struct fuse_backing *fb;
60 
61 	spin_lock(&fc->lock);
62 	fb = idr_remove(&fc->backing_files_map, id);
63 	spin_unlock(&fc->lock);
64 
65 	return fb;
66 }
67 
68 static int fuse_backing_id_free(int id, void *p, void *data)
69 {
70 	struct fuse_backing *fb = p;
71 
72 	WARN_ON_ONCE(refcount_read(&fb->count) != 1);
73 	fuse_backing_free(fb);
74 	return 0;
75 }
76 
77 void fuse_backing_files_free(struct fuse_conn *fc)
78 {
79 	idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL);
80 	idr_destroy(&fc->backing_files_map);
81 }
82 
83 int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
84 {
85 	struct file *file;
86 	struct super_block *backing_sb;
87 	struct fuse_backing *fb = NULL;
88 	int res;
89 
90 	pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
91 
92 	/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
93 	res = -EPERM;
94 	if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
95 		goto out;
96 
97 	res = -EINVAL;
98 	if (map->flags || map->padding)
99 		goto out;
100 
101 	file = fget_raw(map->fd);
102 	res = -EBADF;
103 	if (!file)
104 		goto out;
105 
106 	/* read/write/splice/mmap passthrough only relevant for regular files */
107 	res = d_is_dir(file->f_path.dentry) ? -EISDIR : -EINVAL;
108 	if (!d_is_reg(file->f_path.dentry))
109 		goto out_fput;
110 
111 	backing_sb = file_inode(file)->i_sb;
112 	res = -ELOOP;
113 	if (backing_sb->s_stack_depth >= fc->max_stack_depth)
114 		goto out_fput;
115 
116 	fb = kmalloc_obj(struct fuse_backing);
117 	res = -ENOMEM;
118 	if (!fb)
119 		goto out_fput;
120 
121 	fb->file = file;
122 	fb->cred = get_current_cred();
123 	refcount_set(&fb->count, 1);
124 
125 	res = fuse_backing_id_alloc(fc, fb);
126 	if (res < 0) {
127 		fuse_backing_free(fb);
128 		fb = NULL;
129 	}
130 
131 out:
132 	pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res);
133 
134 	return res;
135 
136 out_fput:
137 	fput(file);
138 	goto out;
139 }
140 
141 int fuse_backing_close(struct fuse_conn *fc, int backing_id)
142 {
143 	struct fuse_backing *fb = NULL;
144 	int err;
145 
146 	pr_debug("%s: backing_id=%d\n", __func__, backing_id);
147 
148 	/* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
149 	err = -EPERM;
150 	if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
151 		goto out;
152 
153 	err = -EINVAL;
154 	if (backing_id <= 0)
155 		goto out;
156 
157 	err = -ENOENT;
158 	fb = fuse_backing_id_remove(fc, backing_id);
159 	if (!fb)
160 		goto out;
161 
162 	fuse_backing_put(fb);
163 	err = 0;
164 out:
165 	pr_debug("%s: fb=0x%p, err=%i\n", __func__, fb, err);
166 
167 	return err;
168 }
169 
170 struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id)
171 {
172 	struct fuse_backing *fb;
173 
174 	rcu_read_lock();
175 	fb = idr_find(&fc->backing_files_map, backing_id);
176 	fb = fuse_backing_get(fb);
177 	rcu_read_unlock();
178 
179 	return fb;
180 }
181