├── Makefile ├── Readme.md ├── flow.png └── pindown.c /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for the kernel security code 3 | # 4 | obj-$(CONFIG_KEYS) += keys/ 5 | subdir-$(CONFIG_SECURITY_SELINUX) += selinux 6 | 7 | # if we don't select a security model, use the default capabilities 8 | ifneq ($(CONFIG_SECURITY),y) 9 | obj-y += commoncap.o 10 | endif 11 | 12 | # Object file lists 13 | obj-$(CONFIG_SECURITY) += security.o dummy.o inode.o 14 | # Must precede capability.o in order to stack properly. 15 | obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o 16 | obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o 17 | obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o 18 | 19 | obj-m += pindown.o 20 | 21 | all: 22 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 23 | 24 | clean: 25 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 26 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | This project uses LSM hooks to enforce a simple access control policy. The implementation associates the 3 | path of a binary file which is allowed to access a protected file. The path of the binary file is stored in the extended attribute 4 | (XAttr) of the inode of the protected file. When a process tries to access this file, the path of the binary which was 5 | loaded during the exec() call of the process is checked against the XAttr attribute ("security.pindown" in this case). If the 6 | path matches then the process is allowed to access the file. Otherwise, access is denied. 7 | 8 | #### PinDOWN LSM implementation 9 | Pindown module that implements the four hooks listed below which are exposed by the Linux Security Modules framework: 10 | * task_alloc_security
11 | * task_free_security
12 | * bprm_set_security
13 | * inode_permission
14 | 15 | This implementation is tested for the linux kernel version 2.6.23. The diagram below shows how the implementation control 16 | access to files using LSM hooks. 17 | ![Access Control using Pindown LSM](https://github.com/atambol/Linux-Security-Module/blob/master/flow.png "Flow of control") 18 | 19 | #### Commands to load the module 20 | * Download the kernel linux-2.6.23. Older distros of linux such as ubuntu 8.04 LTS can be used because it allows loading kernel modules without reboot.
21 | * Copy `pindown.c` and `Makefile` to linux-2.6.23/security directory.
22 | * Run `make all` in the root directory of the kernel repository. This generates the kernel object `security/pindown.ko`.
23 | * To insert the module, run `insmod security/pindown.ko`. The hooks provided in the module should enforce access control.
24 | * Logs can be accessed in the file `/var/log/kern.log`. Check if the modules is loaded using `lsmod` command.
25 | 26 | #### Setting the access control for policy 27 | Following example limits the acces to file `/foo/bar/protected_file` to the program `/usr/bin/vi` only. 28 | Any other program, trying to access the file would be denied access to the protected file.
29 | * Set the XAttr attribute on the protected file.
30 | `sudo setfattr -n security.pindown -v '/usr/bin/vi' /foo/bar/protected_file`
31 | * Check the XAttr attribute
32 | `sudo getfattr -n security.pindown /foo/bar/protected_file`
33 | * Load the `pindown.ko` module as shown in the previous section.
34 | 35 | With that, only `vi` program should be have access to the file. Any other program attempting to access the file would be denied the permission. 36 | 37 | #### References 38 | The project is inspired from the [PinUP](https://enck.org/pubs/acsac08a.pdf) paper.
39 | [Linux Security Module Framework](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.124.5163&rep=rep1&type=pdf) -------------------------------------------------------------------------------- /flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node3/Linux-Security-Module/94ee572d4abe71c2234072a8baedea1ab5d0aa14/flow.png -------------------------------------------------------------------------------- /pindown.c: -------------------------------------------------------------------------------- 1 | /** 2 | * PinDOWN implementation 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #define MAX_PATHLEN 128 44 | 45 | typedef struct pindown_security_t { 46 | char bprm_pathname[MAX_PATHLEN]; 47 | u32 pathlen; 48 | } pindown_security_t; 49 | 50 | 51 | MODULE_LICENSE("GPL"); 52 | 53 | #define INITCONTEXTLEN 100 54 | #define XATTR_SAMPLE_SUFFIX "pindown" 55 | #define XATTR_NAME_SAMPLE XATTR_SECURITY_PREFIX XATTR_SAMPLE_SUFFIX 56 | 57 | extern struct security_operations *security_ops; 58 | 59 | /* Function: get_inode_policy(@inode, @name) 60 | * Description: 61 | * - Utility function for getting the pathname policy from an inode 62 | * - returns pointer to allocated string (needs deallocation) 63 | * Input: 64 | * @inode : the inode 65 | * @name : xattr name to lookup 66 | * Output: 67 | * - returns allocated string of pathname 68 | * - NULL indicates error or not found 69 | * - return value needs to be kfree()'d after this call if not NULL 70 | */ 71 | char *get_inode_policy(struct inode *inode, const char *name) 72 | { 73 | int rc = -1; 74 | char *pathname = NULL; 75 | struct dentry *dentry; 76 | int len = 0; 77 | 78 | printk(KERN_INFO "PinDOWN: get_inode_policy.\n"); 79 | /* Make sure this inode supports the functions we need */ 80 | if (!inode || !inode->i_op || !inode->i_op->getxattr) { 81 | goto out; 82 | } 83 | 84 | /* getxattr requires a dentry */ 85 | dentry = d_find_alias(inode); 86 | if (!dentry) { 87 | goto out; 88 | } 89 | 90 | /* Try default length */ 91 | len = INITCONTEXTLEN; 92 | pathname = (char*)kmalloc(sizeof(char)*len, GFP_KERNEL); 93 | if (!pathname) { 94 | dput(dentry); 95 | goto out; 96 | } 97 | rc = inode->i_op->getxattr(dentry, name, pathname, len*sizeof(char)); 98 | 99 | if (rc == -ERANGE) { 100 | /* Need a larger buffer. Query for the right size */ 101 | rc = inode->i_op->getxattr(dentry, name, NULL, 0); 102 | if (rc < 0) { /* could not get size */ 103 | dput(dentry); 104 | kfree(pathname); 105 | pathname = NULL; 106 | goto out; 107 | } 108 | 109 | /* start over with correct size */ 110 | kfree(pathname); 111 | len = rc / sizeof(char); 112 | pathname = (char*)kmalloc(sizeof(char)*len, GFP_KERNEL); 113 | if (!pathname) { 114 | rc = -ENOMEM; 115 | dput(dentry); 116 | goto out; 117 | } 118 | rc = inode->i_op->getxattr(dentry, name, pathname, len*sizeof(char)); 119 | } 120 | dput(dentry); 121 | 122 | if (rc < 0) { 123 | kfree(pathname); 124 | pathname = NULL; 125 | goto out; 126 | } 127 | 128 | out: 129 | return pathname; 130 | } 131 | 132 | /* Function: pindown_inode_permission(@inode, @mask, @nd) 133 | * Description: 134 | * - LSM Hook .inode_permission() 135 | * - Performs the main access control check on files 136 | * Input: 137 | * @inode : pointer to the inode (object) of the lookup 138 | * @mask : permission mask of the lookup (not used at all) 139 | * @nd : ?? (not used at all) 140 | * Output: 141 | * - returns 0 for access granted, -EACCES for permission denied 142 | */ 143 | int pindown_inode_permission(struct inode *inode, int mask, struct nameidata *nd) 144 | { 145 | 146 | //Initial default allow. Change to default deny once implemented. 147 | int rc = 0; 148 | pindown_security_t *sec = NULL; 149 | char *inode_policy = NULL; 150 | int i; 151 | 152 | /* Don't check this if it is a directory */ 153 | if ((inode->i_mode & S_IFMT) == S_IFDIR) { 154 | rc = 0; 155 | //printk(KERN_INFO "PinDOWN: pindown_inode_permission: Don't check this if it is a directory.\n"); 156 | goto out; 157 | } 158 | 159 | /* Get the inode policy */ 160 | inode_policy = get_inode_policy(inode, "security.pindown"); 161 | if (!inode_policy) { 162 | rc = 0; 163 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: No policy, allowing access.\n"); 164 | goto out; 165 | } 166 | 167 | /* Get the process security info */ 168 | sec = current->security; 169 | if (!sec || sec->bprm_pathname[0] == '\0') { 170 | // Not allowing a program to access a file if the program has not set the security field 171 | rc = -EACCES; 172 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: Not allowing a program to access a file if the program has not set the security field.\n"); 173 | goto out; 174 | } 175 | 176 | /* Compare process security info to inode policy */ 177 | rc = 0; 178 | i=0; 179 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: inode_policy:%s.\n", inode_policy); 180 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: sec->bprm_pathname:%s.\n", sec->bprm_pathname); 181 | while ((i < MAX_PATHLEN) && (inode_policy[i] != '\0') && (sec->bprm_pathname[i] != '\0')) { 182 | if (inode_policy[i] != sec->bprm_pathname[i]) { 183 | rc = -EACCES; 184 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: pathname and filename mismatch.\n"); 185 | goto out; 186 | } 187 | i++; 188 | } 189 | 190 | if ((i <= MAX_PATHLEN) && (inode_policy[i] == sec->bprm_pathname[i]) && (inode_policy[i] == '\0')) { 191 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: allowed.\n"); 192 | rc = 0; 193 | } 194 | else { 195 | // printk(KERN_INFO "PinDOWN: pindown_inode_permission: denied.\n"); 196 | rc = -EACCES; 197 | } 198 | 199 | out: 200 | if (inode_policy) { 201 | kfree(inode_policy); 202 | } 203 | return rc; 204 | } 205 | 206 | 207 | /* Function: pindown_task_alloc_security(@p) 208 | * Description: 209 | * - LSM Hook .task_alloc_security() 210 | * - Allocates @p->security to store the path 211 | * Input: 212 | * @p : pointer to the child task_struct 213 | * Output: 214 | * - @p->security is allocated 215 | * - returns 0 if successful 216 | */ 217 | int pindown_task_alloc_security(struct task_struct * p) 218 | { 219 | int err = 0; 220 | int i; 221 | pindown_security_t *sec = NULL; 222 | pindown_security_t *parent_sec = NULL; // Parent 223 | 224 | printk(KERN_INFO "PinDOWN: pindown_task_alloc_security.\n"); 225 | sec = (pindown_security_t *)kmalloc(sizeof(pindown_security_t), GFP_KERNEL); 226 | if (sec == NULL) { 227 | err = -ENOMEM; 228 | goto out; 229 | } 230 | 231 | /* When we fork, we are still the same application as our 232 | * parent, therefore, it is appropriate to copy the 233 | * parent's digest. On exec(), the digest will be set to 234 | * the new application binary with pindown_bprm_set_security() 235 | */ 236 | 237 | parent_sec = current->security; 238 | if (!parent_sec) { 239 | sec->bprm_pathname[0] = '\0'; 240 | sec->pathlen = 0; 241 | } 242 | else { 243 | //printk(KERN_INFO "PinDOWN: PARENT %s.\n", parent_sec->bprm_pathname); 244 | i = 0; 245 | while ((i < MAX_PATHLEN) && (parent_sec->bprm_pathname[i] != '\0')) { 246 | sec->bprm_pathname[i] = parent_sec->bprm_pathname[i]; 247 | i++; 248 | } 249 | if (i <= MAX_PATHLEN) { 250 | sec->pathlen = i; 251 | } 252 | else { 253 | err = -ENOMEM; 254 | // printk(KERN_INFO "PinDOWN: PATHLEN is too big to store\n"); 255 | goto out; 256 | } 257 | } 258 | p->security = sec; 259 | out: 260 | return err; 261 | } 262 | 263 | /* Function: pindown_task_free_security(@p) 264 | * Description: 265 | * - LSM Hook .task_free_security() 266 | * - Deallocates @p->security 267 | * Input: 268 | * @p : pointer to the child task_struct 269 | * Output: 270 | * - @p->security is deallocated 271 | */ 272 | void pindown_task_free_security(struct task_struct * p) 273 | { 274 | pindown_security_t *sec; 275 | printk(KERN_INFO "PinDOWN: pindown_task_free_security.\n"); 276 | if (!p->security) { 277 | return; 278 | } 279 | sec = p->security; 280 | kfree(sec); 281 | p->security = NULL; 282 | return; 283 | } 284 | 285 | /* Function: pindown_bprm_set_security(@bprm) 286 | * Description: 287 | * - LSM Hook .bprm_set_security() 288 | * - Sets @current->security to the path of the binary 289 | * Input: 290 | * @bprm : pointer to a binary being loaded by the kernel 291 | * Output: 292 | * - @current->security is set to the path of the binary 293 | * - return 0 if the hook is successful and permission is granted 294 | */ 295 | int pindown_bprm_set_security(struct linux_binprm * bprm) 296 | { 297 | int rc = 0; 298 | int i; 299 | pindown_security_t *sec = current->security; 300 | 301 | printk(KERN_INFO "PinDOWN: pindown_bprm_set_security.\n"); 302 | if (sec == NULL) { 303 | rc = pindown_task_alloc_security(current); 304 | } 305 | 306 | /* Set the pathname from the exec()'d binary filename */ 307 | if (!rc) { 308 | if (!bprm || !bprm->filename) { 309 | sec->bprm_pathname[0] = '\0'; 310 | sec->pathlen = 0; 311 | } 312 | else { 313 | // printk(KERN_INFO "PinDOWN: BINARY %s.\n", bprm->filename); 314 | i = 0; 315 | while ((i < MAX_PATHLEN) && (bprm->filename[i] != '\0')) { 316 | sec->bprm_pathname[i] = bprm->filename[i]; 317 | i++; 318 | } 319 | while (i < MAX_PATHLEN) { 320 | sec->bprm_pathname[i] = '\0'; 321 | i++; 322 | } 323 | sec->pathlen = i; 324 | // printk(KERN_INFO "PinDOWN: SAVED %d %s.\n",sec->pathlen ,sec->bprm_pathname); 325 | } 326 | } 327 | else { 328 | //printk(KERN_INFO "PinDOWN: Should I do something if error was returned by pindown_task_alloc_security() ??"); 329 | } 330 | return rc; 331 | } 332 | 333 | 334 | static struct security_operations pindown_ops = { 335 | .task_alloc_security = pindown_task_alloc_security, 336 | .task_free_security = pindown_task_free_security, 337 | .bprm_set_security = pindown_bprm_set_security, 338 | .inode_permission = pindown_inode_permission, 339 | }; 340 | 341 | static __init int pindown_init(void) 342 | { 343 | if (register_security (&pindown_ops)) { 344 | printk("PinDOWN: Unable to register with kernel.\n"); 345 | return 0; 346 | } 347 | printk(KERN_INFO "PinDOWN: Initializing.\n"); 348 | return 0; 349 | } 350 | 351 | static __exit void pindown_exit(void) 352 | { 353 | printk(KERN_INFO "PinDOWN: Exiting.\n"); 354 | unregister_security(&pindown_ops); 355 | } 356 | 357 | module_init(pindown_init); 358 | module_exit(pindown_exit); 359 | --------------------------------------------------------------------------------