xref: /linux/fs/fuse/backing.c (revision 6238729bfce13f94b701766996a5d116d2df8bff)
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