Update to 3.4-final.
[linux-flexiantxendom0-3.2.10.git] / fs / richacl_inode.c
1 /*
2  * Copyright (C) 2010  Novell, Inc.
3  * Written by Andreas Gruenbacher <agruen@suse.de>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; either version 2, or (at your option) any
8  * later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  */
15
16 #include <linux/sched.h>
17 #include <linux/module.h>
18 #include <linux/fs.h>
19 #include <linux/richacl.h>
20
21 /**
22  * richacl_may_create  -  helper for implementing iop->may_create
23  */
24 int
25 richacl_may_create(struct inode *dir, int isdir,
26                 int (*richacl_permission)(struct inode *, unsigned int))
27 {
28         if (IS_RICHACL(dir))
29                 return richacl_permission(dir,
30                                 ACE4_EXECUTE | (isdir ?
31                                 ACE4_ADD_SUBDIRECTORY : ACE4_ADD_FILE));
32         else
33                 return generic_permission(dir, MAY_WRITE | MAY_EXEC);
34 }
35 EXPORT_SYMBOL(richacl_may_create);
36
37 static int
38 check_sticky(struct inode *dir, struct inode *inode)
39 {
40         if (!(dir->i_mode & S_ISVTX))
41                 return 0;
42         if (inode->i_uid == current_fsuid())
43                 return 0;
44         if (dir->i_uid == current_fsuid())
45                 return 0;
46         return !capable(CAP_FOWNER);
47 }
48
49 /**
50  * richacl_may_delete  -  helper for implementing iop->may_delete
51  */
52 int
53 richacl_may_delete(struct inode *dir, struct inode *inode, int replace,
54                    int (*richacl_permission)(struct inode *, unsigned int))
55 {
56         int error;
57
58         if (IS_RICHACL(inode)) {
59                 error = richacl_permission(dir,
60                                 ACE4_EXECUTE | ACE4_DELETE_CHILD);
61                 if (!error && check_sticky(dir, inode))
62                         error = -EPERM;
63                 if (error && !richacl_permission(inode, ACE4_DELETE))
64                         error = 0;
65                 if (!error && replace)
66                         error = richacl_permission(dir,
67                                         ACE4_EXECUTE | (S_ISDIR(inode->i_mode) ?
68                                         ACE4_ADD_SUBDIRECTORY : ACE4_ADD_FILE));
69         } else {
70                 error = generic_permission(dir, MAY_WRITE | MAY_EXEC);
71                 if (!error && check_sticky(dir, inode))
72                         error = -EPERM;
73         }
74
75         return error;
76 }
77 EXPORT_SYMBOL(richacl_may_delete);
78
79 /**
80  * richacl_inode_permission  -  helper for implementing iop->permission
81  * @inode:      inode to check
82  * @acl:        rich acl of the inode (may be NULL)
83  * @mask:       requested access (ACE4_* bitmask)
84  *
85  * This function is supposed to be used by file systems for implementing the
86  * permission inode operation.
87  */
88 int
89 richacl_inode_permission(struct inode *inode, const struct richacl *acl,
90                          unsigned int mask)
91 {
92         if (acl) {
93                 if (!richacl_permission(inode, acl, mask))
94                         return 0;
95         } else {
96                 int mode = inode->i_mode;
97
98                 if (current_fsuid() == inode->i_uid)
99                         mode >>= 6;
100                 else if (in_group_p(inode->i_gid))
101                         mode >>= 3;
102                 if (!(mask & ~richacl_mode_to_mask(mode)))
103                         return 0;
104         }
105
106         /*
107          * Keep in sync with the capability checks in generic_permission().
108          */
109         if (!(mask & ~ACE4_POSIX_MODE_ALL)) {
110                 /*
111                  * Read/write DACs are always overridable.
112                  * Executable DACs are overridable if at
113                  * least one exec bit is set.
114                  */
115                 if (!(mask & ACE4_POSIX_MODE_EXEC) || execute_ok(inode))
116                         if (capable(CAP_DAC_OVERRIDE))
117                                 return 0;
118         }
119         /*
120          * Searching includes executable on directories, else just read.
121          */
122         if (!(mask & ~(ACE4_READ_DATA | ACE4_LIST_DIRECTORY | ACE4_EXECUTE)) &&
123             (S_ISDIR(inode->i_mode) || !(mask & ACE4_EXECUTE)))
124                 if (capable(CAP_DAC_READ_SEARCH))
125                         return 0;
126
127         return -EACCES;
128 }
129 EXPORT_SYMBOL_GPL(richacl_inode_permission);
130
131 /**
132  * richacl_inode_change_ok  -  helper for implementing iop->setattr
133  * @inode:      inode to check
134  * @attr:       requested inode attribute changes
135  * @richacl_permission: permission function taking an inode and ACE4_* flags
136  *
137  * Keep in sync with inode_change_ok().
138  */
139 int
140 richacl_inode_change_ok(struct inode *inode, struct iattr *attr,
141                         int (*richacl_permission)(struct inode *, unsigned int))
142 {
143         unsigned int ia_valid = attr->ia_valid;
144
145         /* If force is set do it anyway. */
146         if (ia_valid & ATTR_FORCE)
147                 return 0;
148
149         /* Make sure a caller can chown. */
150         if ((ia_valid & ATTR_UID) &&
151             (current_fsuid() != inode->i_uid ||
152              attr->ia_uid != inode->i_uid) &&
153             (current_fsuid() != attr->ia_uid ||
154              richacl_permission(inode, ACE4_WRITE_OWNER)) &&
155             !capable(CAP_CHOWN))
156                 goto error;
157
158         /* Make sure caller can chgrp. */
159         if ((ia_valid & ATTR_GID)) {
160                 int in_group = in_group_p(attr->ia_gid);
161                 if ((current_fsuid() != inode->i_uid ||
162                     (!in_group && attr->ia_gid != inode->i_gid)) &&
163                     (!in_group ||
164                      richacl_permission(inode, ACE4_WRITE_OWNER)) &&
165                     !capable(CAP_CHOWN))
166                         goto error;
167         }
168
169         /* Make sure a caller can chmod. */
170         if (ia_valid & ATTR_MODE) {
171                 if (current_fsuid() != inode->i_uid &&
172                     richacl_permission(inode, ACE4_WRITE_ACL) &&
173                     !capable(CAP_FOWNER))
174                         goto error;
175                 /* Also check the setgid bit! */
176                 if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
177                                 inode->i_gid) && !capable(CAP_FSETID))
178                         attr->ia_mode &= ~S_ISGID;
179         }
180
181         /* Check for setting the inode time. */
182         if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET)) {
183                 if (current_fsuid() != inode->i_uid &&
184                     richacl_permission(inode, ACE4_WRITE_ATTRIBUTES) &&
185                     !capable(CAP_FOWNER))
186                         goto error;
187         }
188         return 0;
189 error:
190         return -EPERM;
191 }
192 EXPORT_SYMBOL_GPL(richacl_inode_change_ok);