diff options
author | James Morris <jmorris@namei.org> | 2007-04-26 03:12:05 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-04-27 13:57:33 -0400 |
commit | 057f6c019fff9ee290641d50647359bb8898918e (patch) | |
tree | c051a915e2038825dd2f50f24fc28abb3c4a5e79 | |
parent | 523ded71de0c5e66973335bf99a80edfda9f401b (diff) |
security: prevent permission checking of file removal via sysfs_remove_group()
Prevent permission checking from being performed when the kernel wants to
unconditionally remove a sysfs group, by introducing an kernel-only variant
of lookup_one_len(), lookup_one_len_kern().
Additionally, as sysfs_remove_group() does not check the return value of
the lookup before using it, a BUG_ON has been added to pinpoint the cause
of any problems potentially caused by this (and as a form of annotation).
Signed-off-by: James Morris <jmorris@namei.org>
Cc: Nagendra Singh Tomar <nagendra_tomar@adaptec.com>
Cc: Tejun Heo <htejun@gmail.com>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Cc: Eric Paris <eparis@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | fs/namei.c | 72 | ||||
-rw-r--r-- | fs/sysfs/group.c | 6 | ||||
-rw-r--r-- | include/linux/namei.h | 1 |
3 files changed, 57 insertions, 22 deletions
diff --git a/fs/namei.c b/fs/namei.c index ee60cc4d3453..880052cadbcd 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
@@ -1243,22 +1243,13 @@ int __user_path_lookup_open(const char __user *name, unsigned int lookup_flags, | |||
1243 | return err; | 1243 | return err; |
1244 | } | 1244 | } |
1245 | 1245 | ||
1246 | /* | 1246 | static inline struct dentry *__lookup_hash_kern(struct qstr *name, struct dentry *base, struct nameidata *nd) |
1247 | * Restricted form of lookup. Doesn't follow links, single-component only, | ||
1248 | * needs parent already locked. Doesn't follow mounts. | ||
1249 | * SMP-safe. | ||
1250 | */ | ||
1251 | static struct dentry * __lookup_hash(struct qstr *name, struct dentry * base, struct nameidata *nd) | ||
1252 | { | 1247 | { |
1253 | struct dentry * dentry; | 1248 | struct dentry *dentry; |
1254 | struct inode *inode; | 1249 | struct inode *inode; |
1255 | int err; | 1250 | int err; |
1256 | 1251 | ||
1257 | inode = base->d_inode; | 1252 | inode = base->d_inode; |
1258 | err = permission(inode, MAY_EXEC, nd); | ||
1259 | dentry = ERR_PTR(err); | ||
1260 | if (err) | ||
1261 | goto out; | ||
1262 | 1253 | ||
1263 | /* | 1254 | /* |
1264 | * See if the low-level filesystem might want | 1255 | * See if the low-level filesystem might want |
@@ -1287,35 +1278,76 @@ out: | |||
1287 | return dentry; | 1278 | return dentry; |
1288 | } | 1279 | } |
1289 | 1280 | ||
1281 | /* | ||
1282 | * Restricted form of lookup. Doesn't follow links, single-component only, | ||
1283 | * needs parent already locked. Doesn't follow mounts. | ||
1284 | * SMP-safe. | ||
1285 | */ | ||
1286 | static inline struct dentry * __lookup_hash(struct qstr *name, struct dentry *base, struct nameidata *nd) | ||
1287 | { | ||
1288 | struct dentry *dentry; | ||
1289 | struct inode *inode; | ||
1290 | int err; | ||
1291 | |||
1292 | inode = base->d_inode; | ||
1293 | |||
1294 | err = permission(inode, MAY_EXEC, nd); | ||
1295 | dentry = ERR_PTR(err); | ||
1296 | if (err) | ||
1297 | goto out; | ||
1298 | |||
1299 | dentry = __lookup_hash_kern(name, base, nd); | ||
1300 | out: | ||
1301 | return dentry; | ||
1302 | } | ||
1303 | |||
1290 | static struct dentry *lookup_hash(struct nameidata *nd) | 1304 | static struct dentry *lookup_hash(struct nameidata *nd) |
1291 | { | 1305 | { |
1292 | return __lookup_hash(&nd->last, nd->dentry, nd); | 1306 | return __lookup_hash(&nd->last, nd->dentry, nd); |
1293 | } | 1307 | } |
1294 | 1308 | ||
1295 | /* SMP-safe */ | 1309 | /* SMP-safe */ |
1296 | struct dentry * lookup_one_len(const char * name, struct dentry * base, int len) | 1310 | static inline int __lookup_one_len(const char *name, struct qstr *this, struct dentry *base, int len) |
1297 | { | 1311 | { |
1298 | unsigned long hash; | 1312 | unsigned long hash; |
1299 | struct qstr this; | ||
1300 | unsigned int c; | 1313 | unsigned int c; |
1301 | 1314 | ||
1302 | this.name = name; | 1315 | this->name = name; |
1303 | this.len = len; | 1316 | this->len = len; |
1304 | if (!len) | 1317 | if (!len) |
1305 | goto access; | 1318 | return -EACCES; |
1306 | 1319 | ||
1307 | hash = init_name_hash(); | 1320 | hash = init_name_hash(); |
1308 | while (len--) { | 1321 | while (len--) { |
1309 | c = *(const unsigned char *)name++; | 1322 | c = *(const unsigned char *)name++; |
1310 | if (c == '/' || c == '\0') | 1323 | if (c == '/' || c == '\0') |
1311 | goto access; | 1324 | return -EACCES; |
1312 | hash = partial_name_hash(c, hash); | 1325 | hash = partial_name_hash(c, hash); |
1313 | } | 1326 | } |
1314 | this.hash = end_name_hash(hash); | 1327 | this->hash = end_name_hash(hash); |
1328 | return 0; | ||
1329 | } | ||
1315 | 1330 | ||
1331 | struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) | ||
1332 | { | ||
1333 | int err; | ||
1334 | struct qstr this; | ||
1335 | |||
1336 | err = __lookup_one_len(name, &this, base, len); | ||
1337 | if (err) | ||
1338 | return ERR_PTR(err); | ||
1316 | return __lookup_hash(&this, base, NULL); | 1339 | return __lookup_hash(&this, base, NULL); |
1317 | access: | 1340 | } |
1318 | return ERR_PTR(-EACCES); | 1341 | |
1342 | struct dentry *lookup_one_len_kern(const char *name, struct dentry *base, int len) | ||
1343 | { | ||
1344 | int err; | ||
1345 | struct qstr this; | ||
1346 | |||
1347 | err = __lookup_one_len(name, &this, base, len); | ||
1348 | if (err) | ||
1349 | return ERR_PTR(err); | ||
1350 | return __lookup_hash_kern(&this, base, NULL); | ||
1319 | } | 1351 | } |
1320 | 1352 | ||
1321 | /* | 1353 | /* |
diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index b20951c93761..52eed2a7a5ef 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c | |||
@@ -70,9 +70,11 @@ void sysfs_remove_group(struct kobject * kobj, | |||
70 | { | 70 | { |
71 | struct dentry * dir; | 71 | struct dentry * dir; |
72 | 72 | ||
73 | if (grp->name) | 73 | if (grp->name) { |
74 | dir = lookup_one_len(grp->name, kobj->dentry, | 74 | dir = lookup_one_len_kern(grp->name, kobj->dentry, |
75 | strlen(grp->name)); | 75 | strlen(grp->name)); |
76 | BUG_ON(IS_ERR(dir)); | ||
77 | } | ||
76 | else | 78 | else |
77 | dir = dget(kobj->dentry); | 79 | dir = dget(kobj->dentry); |
78 | 80 | ||
diff --git a/include/linux/namei.h b/include/linux/namei.h index d39a5a67e979..b7dd24917f0d 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h | |||
@@ -82,6 +82,7 @@ extern struct file *nameidata_to_filp(struct nameidata *nd, int flags); | |||
82 | extern void release_open_intent(struct nameidata *); | 82 | extern void release_open_intent(struct nameidata *); |
83 | 83 | ||
84 | extern struct dentry * lookup_one_len(const char *, struct dentry *, int); | 84 | extern struct dentry * lookup_one_len(const char *, struct dentry *, int); |
85 | extern struct dentry *lookup_one_len_kern(const char *, struct dentry *, int); | ||
85 | 86 | ||
86 | extern int follow_down(struct vfsmount **, struct dentry **); | 87 | extern int follow_down(struct vfsmount **, struct dentry **); |
87 | extern int follow_up(struct vfsmount **, struct dentry **); | 88 | extern int follow_up(struct vfsmount **, struct dentry **); |