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