Merge remote-tracking branch 'origin/patch-guilherme'
[procprotect.git] / procprotect.c
1 #include <linux/module.h>
2 #include <linux/moduleparam.h>
3 #include <linux/types.h>
4 #include <linux/kernel.h>
5 #include <linux/version.h>
6 #include <linux/fs_struct.h>
7 #include <linux/fs.h>
8 #include <linux/mm.h>
9 #include <linux/reboot.h>
10 #include <linux/delay.h>
11 #include <linux/proc_fs.h>
12 #include <asm/uaccess.h>
13 #include <linux/sysrq.h>
14 #include <linux/timer.h>
15 #include <linux/time.h>
16 #include <linux/lglock.h>
17 #include <linux/init.h>
18 #include <linux/idr.h>
19 #include <linux/namei.h>
20 #include <linux/bitops.h>
21 #include <linux/mount.h>
22 #include <linux/dcache.h>
23 #include <linux/spinlock.h>
24 #include <linux/completion.h>
25 #include <linux/sched.h>
26 #include <linux/seq_file.h>
27 #include <linux/kprobes.h>
28 #include <linux/kallsyms.h>
29 #include <linux/nsproxy.h>
30
31 #include <linux/magic.h>
32 #include <linux/slab.h>
33 #include <linux/module.h>       /* Specifically, a module */
34 #include <linux/kernel.h>       /* We're doing kernel work */
35 #include <linux/proc_fs.h>      /* Necessary because we use the proc fs */
36
37 #define VERSION_STR "0.0.1"
38
39 #ifndef CONFIG_X86_64
40 #error "This code does not support your architecture"
41 #endif
42
43 static char *aclpath = "procprotect";
44
45 static struct qstr aclqpath;
46
47 module_param(aclpath, charp, 0);
48 MODULE_PARM_DESC(aclpath, "Root of directory that stores acl tags for /proc files.");
49
50 MODULE_AUTHOR("Sapan Bhatia <sapanb@cs.princeton.edu>");
51 MODULE_DESCRIPTION("Lightweight ACLs for /proc.");
52 MODULE_LICENSE("GPL");
53 MODULE_VERSION(VERSION_STR);
54
55 struct procprotect_ctx {
56         struct inode **inode;
57         struct qstr *q;
58         struct path *path;
59         int flags;
60 };
61
62 struct acl_entry {
63         unsigned int ino;
64         struct hlist_node hlist;
65 };
66
67 /*
68    Added by Guilherme Sperb Machado <gsm@machados.org>
69    According to recent changes in the kernel, the nameidata struct
70    became opaque in 3.19.1. So, let's declare it in our implementation.
71    The 'if' was added based on the kernel version since the code would
72    considerably change, so, this is the quick fix. We're not sure if
73    this module is really necessary with recent versions of LXC, related
74    to /proc isolation.
75    Source: https://github.com/torvalds/linux/commit/1f55a6ec940fb45e3edaa52b6e9fc40cf8e18dcb
76    */
77 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,1)
78 struct nameidata {
79         struct path     path;
80         struct qstr     last;
81         struct path     root;
82         struct inode    *inode; /* path.dentry.d_inode */
83         unsigned int    flags;
84         unsigned        seq;
85         int             last_type;
86         unsigned        depth;
87         char *saved_names[MAX_NESTED_LINKS + 1];
88 };
89 #endif
90
91 #define HASH_SIZE (1<<10)
92
93 struct hlist_head procprotect_hash[HASH_SIZE];
94
95 struct proc_dir_entry *proc_entry;
96
97 static int run_acl(unsigned long ino) {
98         struct acl_entry *entry;
99         hlist_for_each_entry_rcu_notrace(entry,
100                                  &procprotect_hash[ino & (HASH_SIZE-1)],
101                                  hlist) {
102                 if (entry->ino==ino) {
103                         return 0;
104                 }
105         }
106         return 1;
107 }
108
109 /*
110    Entry point of intercepted call. We need to do two things here:
111    - Decide if we need the heavier return hook to be called
112    - Save the first argument, which is in a register, for consideration in the return hook
113    */
114 static int lookup_fast_entry(struct kretprobe_instance *ri, struct pt_regs *regs) {
115         int ret = -1;
116         struct procprotect_ctx *ctx;
117         struct nameidata *nd = (struct nameidata *) regs->di;
118         struct dentry *parent;
119         struct inode *pinode;
120
121         if (!nd) return ret;
122         parent = nd->path.dentry;
123
124         if (!parent) return ret;
125         pinode = parent->d_inode;
126
127         if (!pinode || !pinode->i_sb || !current || !current->nsproxy) return ret;
128
129         if (pinode->i_sb->s_magic == PROC_SUPER_MAGIC
130                         && current->nsproxy->mnt_ns!=init_task.nsproxy->mnt_ns) {
131                 ctx = (struct procprotect_ctx *) ri->data;
132                 ctx->inode = (struct inode **)regs->dx;
133                 ctx->flags = nd->flags;
134                 ret = 0;
135         }
136
137         return ret;
138 }
139
140 /* The entry hook ensures that the return hook is only called for
141    accesses to /proc */
142
143 int printed=0;
144
145 static int lookup_fast_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
146 {
147         struct procprotect_ctx *ctx = (struct procprotect_ctx *) ri->data;
148         int ret = regs->ax;
149
150         if (ret==0) {
151                 /* The kernel is going to honor the request. Here's where we step in */
152                 struct inode *inode = *(ctx->inode);
153                 if (!run_acl(inode->i_ino)) {
154                         regs->ax = -EPERM;
155                 }
156         }
157
158
159         return 0;
160 }
161
162 static int lookup_slow_entry(struct kretprobe_instance *ri, struct pt_regs *regs) {
163         int ret = -1;
164         struct procprotect_ctx *ctx;
165         struct nameidata *nd = (struct nameidata *) regs->di;
166         struct path *p = (struct path *) regs->si;
167         struct dentry *parent;
168         struct inode *pinode;
169
170         if (!nd) return ret;
171         parent = nd->path.dentry;
172         if (!parent) return ret;
173         pinode= parent->d_inode;
174
175         if (pinode->i_sb->s_magic == PROC_SUPER_MAGIC
176                         && current->nsproxy->mnt_ns!=init_task.nsproxy->mnt_ns) {
177
178                 ctx = (struct procprotect_ctx *) ri->data;
179                 ctx->q = &nd->last;
180                 ctx->flags = nd->flags;
181                 ctx->path = p;
182                 ret = 0;
183         }
184
185         return ret;
186 }
187
188 /* The entry hook ensures that the return hook is only called for
189    accesses to /proc */
190
191 /*static int print_once = 0;*/
192
193 static int lookup_slow_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
194 {
195         struct procprotect_ctx *ctx;
196         struct inode *inode;
197         int ret;
198
199         if (!ri || !ri->data) {return 0;}
200         ctx = (struct procprotect_ctx *) ri->data;
201
202         ret = regs->ax;
203
204         if (ret==0) {
205                 struct path *p = ctx->path;
206                 if (!p || !p->dentry || !p->dentry->d_inode /* This last check was responsible for the f18 bug*/) {
207                         return 0;
208                 }
209                 inode = p->dentry->d_inode;
210                 if (!run_acl(inode->i_ino)) {
211                         regs->ax = -EPERM;
212                 }
213         }
214
215         return 0;
216 }
217
218 struct open_flags {
219   int open_flag;
220   umode_t mode;
221   int acc_mode;
222   int intent;
223 };
224
225 static struct file *do_last_probe(struct nameidata *nd, struct path *path, struct file *file,
226                                                  struct open_flags *op, const char *pathname) {
227         struct dentry *parent = nd->path.dentry;
228         struct inode *pinode = parent->d_inode;
229         /*struct qstr *q = &nd->last;*/
230
231
232         if (pinode->i_sb->s_magic == PROC_SUPER_MAGIC && current->nsproxy->mnt_ns!=init_task.nsproxy->mnt_ns) {
233                 /*if (!strncmp(q->name,"sysrq-trigger",13)) {
234                         printk(KERN_CRIT "do_last sysrqtrigger: %d",op->open_flag);
235                 }*/
236                 op->open_flag &= ~O_CREAT;
237         }
238         jprobe_return();
239         return file;
240 }
241
242 static struct jprobe dolast_probe = {
243         .entry = do_last_probe
244 };
245
246 static struct kretprobe fast_probe = {
247         .entry_handler = lookup_fast_entry,
248         .handler = lookup_fast_ret,
249         .maxactive = 20,
250         .data_size = sizeof(struct procprotect_ctx)
251 };
252
253 static struct kretprobe slow_probe = {
254         .entry_handler = lookup_slow_entry,
255         .handler = lookup_slow_ret,
256         .maxactive = 20,
257         .data_size = sizeof(struct procprotect_ctx)
258 };
259
260 int once_only = 0;
261
262 static int init_probes(void) {
263         int ret;
264         dolast_probe.kp.addr =
265                 (kprobe_opcode_t *) kallsyms_lookup_name("do_last");
266
267         if (!dolast_probe.kp.addr) {
268                 printk("Couldn't find %s to plant kretprobe\n", "do_last");
269                 return -1;
270         }
271
272         if ((ret = register_jprobe(&dolast_probe)) <0) {
273                 printk("register_jprobe failed, returned %u\n", ret);
274                 return -1;
275         }
276         fast_probe.kp.addr =
277                 (kprobe_opcode_t *) kallsyms_lookup_name("lookup_fast");
278
279         if (!fast_probe.kp.addr) {
280                 printk("Couldn't find %s to plant kretprobe\n", "lookup_fast");
281                 return -1;
282         }
283
284         slow_probe.kp.addr =
285                 (kprobe_opcode_t *) kallsyms_lookup_name("lookup_slow");
286
287         if (!slow_probe.kp.addr) {
288                 printk("Couldn't find %s to plant kretprobe\n", "lookup_slow");
289                 return -1;
290         }
291
292         if ((ret = register_kretprobe(&fast_probe)) <0) {
293                 printk("register_kretprobe failed, returned %d\n", ret);
294                 return -1;
295         }
296
297         printk("Planted kretprobe at %p, handler addr %p\n",
298                         fast_probe.kp.addr, fast_probe.handler);
299
300         if ((ret = register_kretprobe(&slow_probe)) <0) {
301                 printk("register_kretprobe failed, returned %d\n", ret);
302                 return -1;
303         }
304         printk("Planted kretprobe at %p, handler addr %p\n",
305                         slow_probe.kp.addr, slow_probe.handler);
306         return 0;
307 }
308
309 static void add_entry(char *pathname) {
310         struct path path;
311         if (kern_path(pathname, 0, &path)) {
312                 printk(KERN_CRIT "Path lookup failed for %s",pathname);
313         }
314         else {
315                 unsigned int ino = path.dentry->d_inode->i_ino;
316                 struct acl_entry *entry;
317                 entry = kmalloc(GFP_KERNEL, sizeof(struct acl_entry));
318                 entry->ino = ino;
319
320                 if (!entry) {
321                         printk(KERN_CRIT "Could not allocate memory for %s",pathname);
322                 }
323                 else {
324                         if (run_acl(ino)) {
325                                 hlist_add_head_rcu(&entry->hlist,&procprotect_hash[ino&(HASH_SIZE-1)]);
326                                 printk(KERN_CRIT "Added inode %u",ino);
327                         }
328                         else {
329                                 printk(KERN_CRIT "Did not add inode %u, already in list", ino);
330                         }
331                 }
332         }
333
334 }
335
336
337 static void __exit procprotect_exit(void)
338 {
339         struct acl_entry *entry;
340         int i;
341
342         unregister_kretprobe(&fast_probe);
343         unregister_kretprobe(&slow_probe);
344         unregister_jprobe(&dolast_probe);
345
346         for (i=0;i<HASH_SIZE;i++) {
347                 hlist_for_each_entry_rcu(entry,
348                                  &procprotect_hash[i],
349                                  hlist) {
350                         kfree(entry);
351                 }
352         }
353
354         remove_proc_entry("procprotect",NULL);
355         printk("Procprotect: Stopped procprotect.\n");
356 }
357
358
359
360 ssize_t procfile_write(struct file *file, const char *buffer, size_t count, loff_t *data) {
361         char *pathname;
362         pathname = (char *) kmalloc(count, GFP_KERNEL);
363
364         if (!pathname) {
365                 printk(KERN_CRIT "Could not allocate memory for pathname buffer");
366                 return -EFAULT;
367         }
368
369         if (current->nsproxy->mnt_ns!=init_task.nsproxy->mnt_ns)
370                 return -EPERM;
371
372         if (copy_from_user(pathname, buffer, count)) {
373                 return -EFAULT;
374         }
375         if (count && (pathname[count-1]==10 || pathname[count-1]==13)) {
376                 pathname[count-1]='\0';
377         }
378         else
379                 pathname[count]='\0';
380
381         add_entry(pathname);
382
383         if (!once_only) {
384                 once_only=1;
385                 if (init_probes()==-1)
386                         printk(KERN_CRIT "Could not install procprotect probes. Reload module to retry.");
387         }
388         printk(KERN_CRIT "Length of buffer=%d",(int)strlen(pathname));
389         kfree(pathname);
390         return count;
391 }
392
393 static const struct file_operations procprotect_fops = {
394         .owner = THIS_MODULE,
395         .write = procfile_write
396 };
397
398
399 static int __init procprotect_init(void)
400 {
401         int ret = 0;
402         int i;
403
404         printk("Procprotect: starting procprotect version %s with ACLs at path %s.\n",
405                         VERSION_STR, aclpath);
406
407         for(i=0;i<HASH_SIZE;i++) {
408                 INIT_HLIST_HEAD(&procprotect_hash[i]);
409         }
410
411         aclqpath.name = aclpath;
412         aclqpath.len = strnlen(aclpath, PATH_MAX);
413
414         proc_entry = proc_create("procprotect", 0644, NULL, &procprotect_fops);
415
416         return ret;
417 }
418
419
420
421 module_init(procprotect_init);
422 module_exit(procprotect_exit);