aboutsummaryrefslogtreecommitdiff
path: root/security/trustees/security.c
blob: bbc9888624385d1446f2ae217992bb3ab18cdf20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
/*
 * Trustees ACL Project
 *
 * Copyright (c) 1999-2000 Vyacheslav Zavadsky
 * Copyright (c) 2004 Andrew Ruder (aeruder@ksu.edu)
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License as
 *	published by the Free Software Foundation, version 2.
 *
 * The security module (LSM API) component of the trustees system
 *
 * One quick note: generally security modules with the LSM are supposed
 * to be solely restrictive modules.  Unless the trustees module were to
 * require that people set all files rwx by all, it could not function
 * as it is meant to function as a solely restrictive module.
 *
 * To compensate, every process is given the capability CAP_DAC_OVERRIDE.
 * In other words, every process is first given full rights to the filesystem.
 * This is the only non-restricting portion of this module, since it -does-
 * in fact give additional permissions.  However, in the inode_permission hook,
 * any rights the user should not have are taken away.
 *
 * Side effects: Posix ACLs or other filesystem-specific permissions are not
 * honored.  Trustees ACLs can (and do) take into account the standard unix
 * permissions, but any permissions further than that are difficult, to say
 * the least, to take into account.  I, personally, do not find this to
 * be a problem since if you are using Trustees ACLs, why also require the use
 * of another ACL system?
 */

#include <linux/security.h>
#include <linux/capability.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/nsproxy.h>
#include <linux/cred.h>
#include <linux/mnt_namespace.h>

#include "internal.h"

static int trustees_capable(struct task_struct *tsk, const struct cred *cred,
			    int cap,
			    int audit);
static int trustees_inode_permission(struct inode *inode, int mask);

/* Checks if user has access to the inode due to root status
 */
static inline int has_root_perm(struct inode *inode, int mask)
{
	umode_t mode = inode->i_mode;

	if (!(mask & MAY_EXEC) || (mode & S_IXUGO) || S_ISDIR(mode))
		if (current_fsuid() == 0)
			return 0;

	return -EACCES;
}

/* The logic for this was mostly stolen from vfs_permission.  The security API
 * doesn't give a good way to use the actual vfs_permission for this since our
 * CAP_DAC_OVERRIDE causes it to always return 0.  But if we didn't return
 * CAP_DAC_OVERRIDE, we'd never get to handle permissions!  Since we don't need
 * to handle capabilities and dealing with ACLs with trustees loaded isn't an
 * issue for me, the function ends up being pretty simple.
 */

static inline int has_unix_perm(struct inode *inode, int mask)
{
	umode_t mode = inode->i_mode;
	mask &= ~MAY_APPEND;

	if (current_fsuid() == inode->i_uid)
		mode >>= 6;
	else if (in_group_p(inode->i_gid))
		mode >>= 3;

	if (((mode & mask & (MAY_READ | MAY_WRITE | MAY_EXEC)) == mask))
		return 0;

	return -EACCES;
}

/* Find a vfsmount given an inode */
static inline struct vfsmount *find_inode_mnt(struct inode *inode)
{
	struct mnt_namespace *ns = NULL;
	struct vfsmount *mnt = NULL;

	if (!inode->i_sb) {
		TS_ERR_MSG("Inode without a i_sb entry?");
		return NULL;
	}

	/* Okay, we need to find the vfsmount by looking
	 * at the namespace now.
	 */
	task_lock(current);
	if (current->nsproxy) {
		ns = current->nsproxy->mnt_ns;
		if (ns)
			get_mnt_ns(ns);
	}
	task_unlock(current);

	if (!ns)
		return NULL;

	list_for_each_entry(mnt, &ns->list, mnt_list) {
		if (mnt->mnt_sb == inode->i_sb && mnt->mnt_devname) {
			mntget(mnt);
			goto out;
		}
	}

  out:
	put_mnt_ns(ns);

	return mnt;
}

/* Find a dentry given an inode */
static inline struct dentry *find_inode_dentry(struct inode *inode)
{
	struct dentry *dentry;

	dentry = d_find_alias(inode);

	return dentry;
}

/* Find a path (dentry/vfsmount pair) given an inode
 */
static inline int find_inode_path(struct inode *inode, struct path *path)
{
	int ret = 0;

	path->dentry = NULL;
	path->mnt = NULL;

	path->mnt = find_inode_mnt(inode);
	if (unlikely(!path->mnt)) {
		TS_ERR_MSG("inode does not have a mnt!\n");
		goto error_out;
	}

	path->dentry = find_inode_dentry(inode);
	if (unlikely(!path->dentry)) {
		/* Most of the time when this happens, it is the /
		 * If it is not, we need to dump as much information
		 * as possible on it and dump it to logs, because
		 * I'm really not sure how it happens.
		 */
		if (path->mnt->mnt_root && inode == path->mnt->mnt_root->d_inode) {
			path->dentry = dget(path->mnt->mnt_root);
		} else {
			/* I have seen this happen once but I did not have any
			 * way to see what caused it.  I am gonna dump_stack
			 * until I have that happen again to see if the cause
			 * is something that I need to worry about.
			 */
			dump_stack();	/* DEBUG FIXME */
			TS_ERR_MSG("Inode number: %ld\n", inode->i_ino);
			TS_ERR_MSG("dentry does not exist!\n");
			goto error_mnt_out;
		}
	}

	goto out;

error_mnt_out:
	mntput(path->mnt);
	path->mnt = NULL;

error_out:
	ret = 1;

out:
	return ret;
}

/*
 * Return 1 if they are under the same set of trustees
 * otherwise return 0.  In the case that we are handling
 * a directory, we also check to see if there are subdirectories
 * with trustees.
 */
static inline int have_same_trustees_rename(struct path *path1, struct path *path2)
{
	char *filename1, *filename2;
	int depth1, depth2;
	struct trustee_hash_element *deep1, *deep2;
	int is_dir;
	int ret = 0;

	filename1 = trustees_filename_for_dentry(path1->dentry, &depth1, 1);
	if (!filename1) {
		TS_ERR_MSG("Couldn't allocate filename\n");
		goto out;
	}

	filename2 = trustees_filename_for_dentry(path2->dentry, &depth2, 1);
	if (!filename2) {
		TS_ERR_MSG("Couldn't allocate filename\n");
		goto out_file_name;
	}

	is_dir = S_ISDIR(path1->dentry->d_inode->i_mode);

	read_lock(&trustee_hash_lock);
	trustee_perm(path1, filename1, ret, depth1, is_dir, &deep1);
	trustee_perm(path2, filename2, ret, depth2, is_dir, &deep2);
	if (deep1 == deep2) {
		ret = 1;
		if (is_dir) {
			if (trustee_has_child(path1->mnt, filename1) ||
				trustee_has_child(path2->mnt, filename2)) ret = 0;
		}
	}
	read_unlock(&trustee_hash_lock);

	kfree(filename2);
  out_file_name:
	kfree(filename1);
  out:

	return ret;
}

static int trustees_inode_rename(struct inode *old_dir,
				 struct dentry *old_dentry,
				 struct inode *new_dir,
				 struct dentry *new_dentry);
static int trustees_inode_link(struct dentry *old_dentry,
			       struct inode *dir,
			       struct dentry *new_dentry);

/* Structure where we fill in the various hooks we are implementing in this module
 */
struct security_operations trustees_security_ops = {
	.capable = trustees_capable,
	.inode_permission = trustees_inode_permission,
	.inode_link = trustees_inode_link,
	.inode_rename = trustees_inode_rename
};

#define ALL_MAYS (MAY_WRITE | MAY_EXEC | MAY_READ)
/* Converts a trustee_mask to a normal unix mask
 */
static int inline trustee_mask_to_normal_mask(int mask, int isdir)
{
	int r = 0;
	if ((mask & TRUSTEE_READ_MASK) && !isdir)
		r |= MAY_READ;
	if ((mask & TRUSTEE_READ_DIR_MASK) && isdir)
		r |= MAY_READ;
	if (mask & TRUSTEE_WRITE_MASK)
		r |= MAY_WRITE;
	if ((mask & TRUSTEE_BROWSE_MASK) && isdir)
		r |= MAY_EXEC;
	if ((mask & TRUSTEE_EXECUTE_MASK) && !isdir)
		r |= MAY_EXEC;
	return r;
}

/* This is the meat of the permissions checking.  First it checks for root,
 * otherwise it first checks for any errors finding the dentry/vfsmount for
 * the inode, and then it looks up the dentry in the trustees hash.
 */
static int trustees_inode_permission(struct inode *inode, int mask)
{
	struct path path;
	char *file_name;
	int is_dir;
	int ret;
	int depth;
	int amask;
	int dmask;
	umode_t mode = inode->i_mode;

	if (has_root_perm(inode, mask) == 0)
		return 0;

	ret = has_unix_perm(inode, mask);

	if (find_inode_path(inode, &path)) {
		return -EACCES;
	}

	file_name = trustees_filename_for_dentry(path.dentry, &depth, 1);
	if (!file_name) {
		TS_ERR_MSG("Couldn't allocate filename\n");
		ret = -EACCES;
		goto out_path;
	}

	is_dir = S_ISDIR(inode->i_mode);

	read_lock(&trustee_hash_lock);
	amask = trustee_perm(&path, file_name, ret, depth, is_dir,
			     (struct trustee_hash_element **)NULL);
	read_unlock(&trustee_hash_lock);
	dmask = amask >> TRUSTEE_NUM_ACL_BITS;

	/* no permission if denied */
	if (trustee_mask_to_normal_mask(dmask, is_dir) & mask & ALL_MAYS) {
		ret = -EACCES;
		goto out;
	}
	/* use unix perms */
	if (!(dmask & TRUSTEE_USE_UNIX_MASK) &&
	    (amask & TRUSTEE_USE_UNIX_MASK) && (!ret))
		goto out;

	/* if the file isn't executable, then the trustees shouldn't
	 * make it executable
	 */
	if ((mask & MAY_EXEC) && !(mode & S_IXOTH) &&
	    !((mode >> 3) & S_IXOTH) & !((mode >> 6) & S_IXOTH) &&
	    (!is_dir)) {
		ret = -EACCES;
		goto out;
	}
	/* Check trustees for permission
	 */
	if ((trustee_mask_to_normal_mask(amask, is_dir) & mask & ALL_MAYS)
	    == mask) {
		ret = 0;
		goto out;
	} else
		ret = -EACCES;

out:
	kfree(file_name);
out_path:
	path_put(&path);

	return ret;
}

/* We should only allow hard links under one of two conditions:
 *   1. Its in the same trustee
 *        - if the two dentries are covered by the same trustee, there shouldn't
 *          be much of a problem with allowing the hardlink to occur.
 *   2. fsuid = 0
 */
static int trustees_inode_link(struct dentry *old_dentry,
			       struct inode *dir,
			       struct dentry *new_dentry)
{
	int ret = -EXDEV;
	struct path path1;
	struct path path2;

	if (current_fsuid() == 0)
		return 0;

	path1.dentry = dget(old_dentry);
	path1.mnt = find_inode_mnt(old_dentry->d_inode);
	path2.dentry = dget(new_dentry);
	path2.mnt = mntget(path1.mnt);

	if (have_same_trustees_rename(&path1, &path2))
		ret = 0;

	path_put(&path1);
	path_put(&path2);

	return ret;
}

/* We have a few renames to protect against:
 *   1. Any file or directory that is affected by different trustees at its
 *      old location than at its new location.
 *   2. In the case of a directory, we should protect against moving a directory
 *      that has trustees set inside of it.
 *
 * In any case above, we return -EXDEV which signifies to the calling program that
 * the files are on different devices, and assuming the program is written correctly
 * it should then handle the situation by copying the files and removing the originals
 * ( which will then use the trustees permissions as they are meant to be used )
 */
static int trustees_inode_rename(struct inode *old_dir,
				 struct dentry *old_dentry,
				 struct inode *new_dir,
				 struct dentry *new_dentry)
{
	return trustees_inode_link(old_dentry, new_dir, new_dentry);
}

/* Return CAP_DAC_OVERRIDE on everything.  We want to handle our own
 * permissions (overriding those normally allowed by unix permissions)
 */
static int trustees_capable(struct task_struct *tsk, const struct cred *cred,
			    int cap,
			    int audit)
{
	if (cap == CAP_DAC_OVERRIDE)
		return 0;

	return cap_capable(tsk, cred, cap, audit);
}

/* Register the security module
 */
int trustees_init_security(void)
{
	if (register_security(&trustees_security_ops)) {
		TS_ERR_MSG("Could not register security component\n");
		return -EINVAL;
	}

	return 0;
}