aboutsummaryrefslogtreecommitdiffstats
path: root/fs/fuse
diff options
context:
space:
mode:
authorMiklos Szeredi <miklos@szeredi.hu>2005-09-09 16:10:26 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2005-09-09 17:03:44 -0400
commitd8a5ba45457e4a22aa39c939121efd7bb6c76672 (patch)
tree686aa90d8c953326b8d2eeef9352e456cdb0ad52 /fs/fuse
parent04578f174f43d29b569500f01ba772afa4016330 (diff)
[PATCH] FUSE - core
This patch adds FUSE core. This contains the following files: o inode.c - superblock operations (alloc_inode, destroy_inode, read_inode, clear_inode, put_super, show_options) - registers FUSE filesystem o fuse_i.h - private header file Requirements ============ The most important difference between orinary filesystems and FUSE is the fact, that the filesystem data/metadata is provided by a userspace process run with the privileges of the mount "owner" instead of the kernel, or some remote entity usually running with elevated privileges. The security implication of this is that a non-privileged user must not be able to use this capability to compromise the system. Obvious requirements arising from this are: - mount owner should not be able to get elevated privileges with the help of the mounted filesystem - mount owner should not be able to induce undesired behavior in other users' or the super user's processes - mount owner should not get illegitimate access to information from other users' and the super user's processes These are currently ensured with the following constraints: 1) mount is only allowed to directory or file which the mount owner can modify without limitation (write access + no sticky bit for directories) 2) nosuid,nodev mount options are forced 3) any process running with fsuid different from the owner is denied all access to the filesystem 1) and 2) are ensured by the "fusermount" mount utility which is a setuid root application doing the actual mount operation. 3) is ensured by a check in the permission() method in kernel I started thinking about doing 3) in a different way because Christoph H. made a big deal out of it, saying that FUSE is unacceptable into mainline in this form. The suggested use of private namespaces would be OK, but in their current form have many limitations that make their use impractical (as discussed in this thread). Suggested improvements that would address these limitations: - implement shared subtrees - allow a process to join an existing namespace (make namespaces first-class objects) - implement the namespace creation/joining in a PAM module With all that in place the check of owner against current->fsuid may be removed from the FUSE kernel module, without compromising the security requirements. Suid programs still interesting questions, since they get access even to the private namespace causing some information leak (exact order/timing of filesystem operations performed), giving some ptrace-like capabilities to unprivileged users. BTW this problem is not strictly limited to the namespace approach, since suid programs setting fsuid and accessing users' files will succeed with the current approach too. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs/fuse')
-rw-r--r--fs/fuse/Makefile7
-rw-r--r--fs/fuse/fuse_i.h89
-rw-r--r--fs/fuse/inode.c428
3 files changed, 524 insertions, 0 deletions
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
new file mode 100644
index 000000000000..9c3e4cc7b1a6
--- /dev/null
+++ b/fs/fuse/Makefile
@@ -0,0 +1,7 @@
1#
2# Makefile for the FUSE filesystem.
3#
4
5obj-$(CONFIG_FUSE_FS) += fuse.o
6
7fuse-objs := inode.o
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
new file mode 100644
index 000000000000..eed6e89ce01f
--- /dev/null
+++ b/fs/fuse/fuse_i.h
@@ -0,0 +1,89 @@
1/*
2 FUSE: Filesystem in Userspace
3 Copyright (C) 2001-2005 Miklos Szeredi <miklos@szeredi.hu>
4
5 This program can be distributed under the terms of the GNU GPL.
6 See the file COPYING.
7*/
8
9#include <linux/fuse.h>
10#include <linux/fs.h>
11#include <linux/wait.h>
12#include <linux/list.h>
13#include <linux/spinlock.h>
14#include <linux/mm.h>
15#include <linux/backing-dev.h>
16#include <asm/semaphore.h>
17
18/** FUSE inode */
19struct fuse_inode {
20 /** Inode data */
21 struct inode inode;
22
23 /** Unique ID, which identifies the inode between userspace
24 * and kernel */
25 u64 nodeid;
26
27 /** Time in jiffies until the file attributes are valid */
28 unsigned long i_time;
29};
30
31/**
32 * A Fuse connection.
33 *
34 * This structure is created, when the filesystem is mounted, and is
35 * destroyed, when the client device is closed and the filesystem is
36 * unmounted.
37 */
38struct fuse_conn {
39 /** The superblock of the mounted filesystem */
40 struct super_block *sb;
41
42 /** The user id for this mount */
43 uid_t user_id;
44
45 /** Backing dev info */
46 struct backing_dev_info bdi;
47};
48
49static inline struct fuse_conn **get_fuse_conn_super_p(struct super_block *sb)
50{
51 return (struct fuse_conn **) &sb->s_fs_info;
52}
53
54static inline struct fuse_conn *get_fuse_conn_super(struct super_block *sb)
55{
56 return *get_fuse_conn_super_p(sb);
57}
58
59static inline struct fuse_conn *get_fuse_conn(struct inode *inode)
60{
61 return get_fuse_conn_super(inode->i_sb);
62}
63
64static inline struct fuse_inode *get_fuse_inode(struct inode *inode)
65{
66 return container_of(inode, struct fuse_inode, inode);
67}
68
69static inline u64 get_node_id(struct inode *inode)
70{
71 return get_fuse_inode(inode)->nodeid;
72}
73
74/**
75 * This is the single global spinlock which protects FUSE's structures
76 *
77 * The following data is protected by this lock:
78 *
79 * - the s_fs_info field of the super block
80 * - the sb (super_block) field in fuse_conn
81 */
82extern spinlock_t fuse_lock;
83
84/**
85 * Check if the connection can be released, and if yes, then free the
86 * connection structure
87 */
88void fuse_release_conn(struct fuse_conn *fc);
89
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
new file mode 100644
index 000000000000..ea6339c2b6a1
--- /dev/null
+++ b/fs/fuse/inode.c
@@ -0,0 +1,428 @@
1/*
2 FUSE: Filesystem in Userspace
3 Copyright (C) 2001-2005 Miklos Szeredi <miklos@szeredi.hu>
4
5 This program can be distributed under the terms of the GNU GPL.
6 See the file COPYING.
7*/
8
9#include "fuse_i.h"
10
11#include <linux/pagemap.h>
12#include <linux/slab.h>
13#include <linux/file.h>
14#include <linux/mount.h>
15#include <linux/seq_file.h>
16#include <linux/init.h>
17#include <linux/module.h>
18#include <linux/moduleparam.h>
19#include <linux/parser.h>
20#include <linux/statfs.h>
21
22MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
23MODULE_DESCRIPTION("Filesystem in Userspace");
24MODULE_LICENSE("GPL");
25
26spinlock_t fuse_lock;
27static kmem_cache_t *fuse_inode_cachep;
28static int mount_count;
29
30static int mount_max = 1000;
31module_param(mount_max, int, 0644);
32MODULE_PARM_DESC(mount_max, "Maximum number of FUSE mounts allowed, if -1 then unlimited (default: 1000)");
33
34#define FUSE_SUPER_MAGIC 0x65735546
35
36struct fuse_mount_data {
37 int fd;
38 unsigned rootmode;
39 unsigned user_id;
40};
41
42static struct inode *fuse_alloc_inode(struct super_block *sb)
43{
44 struct inode *inode;
45 struct fuse_inode *fi;
46
47 inode = kmem_cache_alloc(fuse_inode_cachep, SLAB_KERNEL);
48 if (!inode)
49 return NULL;
50
51 fi = get_fuse_inode(inode);
52 fi->i_time = jiffies - 1;
53 fi->nodeid = 0;
54
55 return inode;
56}
57
58static void fuse_destroy_inode(struct inode *inode)
59{
60 kmem_cache_free(fuse_inode_cachep, inode);
61}
62
63static void fuse_read_inode(struct inode *inode)
64{
65 /* No op */
66}
67
68static void fuse_clear_inode(struct inode *inode)
69{
70}
71
72void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr)
73{
74 if (S_ISREG(inode->i_mode) && i_size_read(inode) != attr->size)
75 invalidate_inode_pages(inode->i_mapping);
76
77 inode->i_ino = attr->ino;
78 inode->i_mode = (inode->i_mode & S_IFMT) + (attr->mode & 07777);
79 inode->i_nlink = attr->nlink;
80 inode->i_uid = attr->uid;
81 inode->i_gid = attr->gid;
82 i_size_write(inode, attr->size);
83 inode->i_blksize = PAGE_CACHE_SIZE;
84 inode->i_blocks = attr->blocks;
85 inode->i_atime.tv_sec = attr->atime;
86 inode->i_atime.tv_nsec = attr->atimensec;
87 inode->i_mtime.tv_sec = attr->mtime;
88 inode->i_mtime.tv_nsec = attr->mtimensec;
89 inode->i_ctime.tv_sec = attr->ctime;
90 inode->i_ctime.tv_nsec = attr->ctimensec;
91}
92
93static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
94{
95 inode->i_mode = attr->mode & S_IFMT;
96 i_size_write(inode, attr->size);
97}
98
99static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
100{
101 unsigned long nodeid = *(unsigned long *) _nodeidp;
102 if (get_node_id(inode) == nodeid)
103 return 1;
104 else
105 return 0;
106}
107
108static int fuse_inode_set(struct inode *inode, void *_nodeidp)
109{
110 unsigned long nodeid = *(unsigned long *) _nodeidp;
111 get_fuse_inode(inode)->nodeid = nodeid;
112 return 0;
113}
114
115struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid,
116 int generation, struct fuse_attr *attr, int version)
117{
118 struct inode *inode;
119 struct fuse_conn *fc = get_fuse_conn_super(sb);
120 int retried = 0;
121
122 retry:
123 inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
124 if (!inode)
125 return NULL;
126
127 if ((inode->i_state & I_NEW)) {
128 inode->i_generation = generation;
129 inode->i_data.backing_dev_info = &fc->bdi;
130 fuse_init_inode(inode, attr);
131 unlock_new_inode(inode);
132 } else if ((inode->i_mode ^ attr->mode) & S_IFMT) {
133 BUG_ON(retried);
134 /* Inode has changed type, any I/O on the old should fail */
135 make_bad_inode(inode);
136 iput(inode);
137 retried = 1;
138 goto retry;
139 }
140
141 fuse_change_attributes(inode, attr);
142 inode->i_version = version;
143 return inode;
144}
145
146static void fuse_put_super(struct super_block *sb)
147{
148 struct fuse_conn *fc = get_fuse_conn_super(sb);
149
150 spin_lock(&fuse_lock);
151 mount_count --;
152 fc->sb = NULL;
153 fc->user_id = 0;
154 fuse_release_conn(fc);
155 *get_fuse_conn_super_p(sb) = NULL;
156 spin_unlock(&fuse_lock);
157}
158
159enum {
160 OPT_FD,
161 OPT_ROOTMODE,
162 OPT_USER_ID,
163 OPT_DEFAULT_PERMISSIONS,
164 OPT_ALLOW_OTHER,
165 OPT_ALLOW_ROOT,
166 OPT_KERNEL_CACHE,
167 OPT_ERR
168};
169
170static match_table_t tokens = {
171 {OPT_FD, "fd=%u"},
172 {OPT_ROOTMODE, "rootmode=%o"},
173 {OPT_USER_ID, "user_id=%u"},
174 {OPT_DEFAULT_PERMISSIONS, "default_permissions"},
175 {OPT_ALLOW_OTHER, "allow_other"},
176 {OPT_ALLOW_ROOT, "allow_root"},
177 {OPT_KERNEL_CACHE, "kernel_cache"},
178 {OPT_ERR, NULL}
179};
180
181static int parse_fuse_opt(char *opt, struct fuse_mount_data *d)
182{
183 char *p;
184 memset(d, 0, sizeof(struct fuse_mount_data));
185 d->fd = -1;
186
187 while ((p = strsep(&opt, ",")) != NULL) {
188 int token;
189 int value;
190 substring_t args[MAX_OPT_ARGS];
191 if (!*p)
192 continue;
193
194 token = match_token(p, tokens, args);
195 switch (token) {
196 case OPT_FD:
197 if (match_int(&args[0], &value))
198 return 0;
199 d->fd = value;
200 break;
201
202 case OPT_ROOTMODE:
203 if (match_octal(&args[0], &value))
204 return 0;
205 d->rootmode = value;
206 break;
207
208 case OPT_USER_ID:
209 if (match_int(&args[0], &value))
210 return 0;
211 d->user_id = value;
212 break;
213
214 default:
215 return 0;
216 }
217 }
218 if (d->fd == -1)
219 return 0;
220
221 return 1;
222}
223
224static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt)
225{
226 struct fuse_conn *fc = get_fuse_conn_super(mnt->mnt_sb);
227
228 seq_printf(m, ",user_id=%u", fc->user_id);
229 return 0;
230}
231
232void fuse_release_conn(struct fuse_conn *fc)
233{
234 kfree(fc);
235}
236
237static struct fuse_conn *new_conn(void)
238{
239 struct fuse_conn *fc;
240
241 fc = kmalloc(sizeof(*fc), GFP_KERNEL);
242 if (fc != NULL) {
243 memset(fc, 0, sizeof(*fc));
244 fc->sb = NULL;
245 fc->user_id = 0;
246 fc->bdi.ra_pages = (VM_MAX_READAHEAD * 1024) / PAGE_CACHE_SIZE;
247 fc->bdi.unplug_io_fn = default_unplug_io_fn;
248 }
249 return fc;
250}
251
252static struct fuse_conn *get_conn(struct file *file, struct super_block *sb)
253{
254 struct fuse_conn *fc;
255
256 fc = new_conn();
257 if (fc == NULL)
258 return NULL;
259 spin_lock(&fuse_lock);
260 fc->sb = sb;
261 spin_unlock(&fuse_lock);
262 return fc;
263}
264
265static struct inode *get_root_inode(struct super_block *sb, unsigned mode)
266{
267 struct fuse_attr attr;
268 memset(&attr, 0, sizeof(attr));
269
270 attr.mode = mode;
271 attr.ino = FUSE_ROOT_ID;
272 return fuse_iget(sb, 1, 0, &attr, 0);
273}
274
275static struct super_operations fuse_super_operations = {
276 .alloc_inode = fuse_alloc_inode,
277 .destroy_inode = fuse_destroy_inode,
278 .read_inode = fuse_read_inode,
279 .clear_inode = fuse_clear_inode,
280 .put_super = fuse_put_super,
281 .show_options = fuse_show_options,
282};
283
284static int inc_mount_count(void)
285{
286 int success = 0;
287 spin_lock(&fuse_lock);
288 mount_count ++;
289 if (mount_max == -1 || mount_count <= mount_max)
290 success = 1;
291 spin_unlock(&fuse_lock);
292 return success;
293}
294
295static int fuse_fill_super(struct super_block *sb, void *data, int silent)
296{
297 struct fuse_conn *fc;
298 struct inode *root;
299 struct fuse_mount_data d;
300 struct file *file;
301 int err;
302
303 if (!parse_fuse_opt((char *) data, &d))
304 return -EINVAL;
305
306 sb->s_blocksize = PAGE_CACHE_SIZE;
307 sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
308 sb->s_magic = FUSE_SUPER_MAGIC;
309 sb->s_op = &fuse_super_operations;
310 sb->s_maxbytes = MAX_LFS_FILESIZE;
311
312 file = fget(d.fd);
313 if (!file)
314 return -EINVAL;
315
316 fc = get_conn(file, sb);
317 fput(file);
318 if (fc == NULL)
319 return -EINVAL;
320
321 fc->user_id = d.user_id;
322
323 *get_fuse_conn_super_p(sb) = fc;
324
325 err = -ENFILE;
326 if (!inc_mount_count() && current->uid != 0)
327 goto err;
328
329 err = -ENOMEM;
330 root = get_root_inode(sb, d.rootmode);
331 if (root == NULL)
332 goto err;
333
334 sb->s_root = d_alloc_root(root);
335 if (!sb->s_root) {
336 iput(root);
337 goto err;
338 }
339 return 0;
340
341 err:
342 spin_lock(&fuse_lock);
343 mount_count --;
344 fc->sb = NULL;
345 fuse_release_conn(fc);
346 spin_unlock(&fuse_lock);
347 *get_fuse_conn_super_p(sb) = NULL;
348 return err;
349}
350
351static struct super_block *fuse_get_sb(struct file_system_type *fs_type,
352 int flags, const char *dev_name,
353 void *raw_data)
354{
355 return get_sb_nodev(fs_type, flags, raw_data, fuse_fill_super);
356}
357
358static struct file_system_type fuse_fs_type = {
359 .owner = THIS_MODULE,
360 .name = "fuse",
361 .get_sb = fuse_get_sb,
362 .kill_sb = kill_anon_super,
363};
364
365static void fuse_inode_init_once(void *foo, kmem_cache_t *cachep,
366 unsigned long flags)
367{
368 struct inode * inode = foo;
369
370 if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
371 SLAB_CTOR_CONSTRUCTOR)
372 inode_init_once(inode);
373}
374
375static int __init fuse_fs_init(void)
376{
377 int err;
378
379 err = register_filesystem(&fuse_fs_type);
380 if (err)
381 printk("fuse: failed to register filesystem\n");
382 else {
383 fuse_inode_cachep = kmem_cache_create("fuse_inode",
384 sizeof(struct fuse_inode),
385 0, SLAB_HWCACHE_ALIGN,
386 fuse_inode_init_once, NULL);
387 if (!fuse_inode_cachep) {
388 unregister_filesystem(&fuse_fs_type);
389 err = -ENOMEM;
390 }
391 }
392
393 return err;
394}
395
396static void fuse_fs_cleanup(void)
397{
398 unregister_filesystem(&fuse_fs_type);
399 kmem_cache_destroy(fuse_inode_cachep);
400}
401
402static int __init fuse_init(void)
403{
404 int res;
405
406 printk("fuse init (API version %i.%i)\n",
407 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
408
409 spin_lock_init(&fuse_lock);
410 res = fuse_fs_init();
411 if (res)
412 goto err;
413
414 return 0;
415
416 err:
417 return res;
418}
419
420static void __exit fuse_exit(void)
421{
422 printk(KERN_DEBUG "fuse exit\n");
423
424 fuse_fs_cleanup();
425}
426
427module_init(fuse_init);
428module_exit(fuse_exit);