diff options
-rw-r--r-- | Documentation/sysctl/fs.txt | 42 | ||||
-rw-r--r-- | fs/namei.c | 122 | ||||
-rw-r--r-- | include/linux/fs.h | 2 | ||||
-rw-r--r-- | kernel/sysctl.c | 18 |
4 files changed, 184 insertions, 0 deletions
diff --git a/Documentation/sysctl/fs.txt b/Documentation/sysctl/fs.txt index 13d6166d7a27..d4a372e75750 100644 --- a/Documentation/sysctl/fs.txt +++ b/Documentation/sysctl/fs.txt | |||
@@ -32,6 +32,8 @@ Currently, these files are in /proc/sys/fs: | |||
32 | - nr_open | 32 | - nr_open |
33 | - overflowuid | 33 | - overflowuid |
34 | - overflowgid | 34 | - overflowgid |
35 | - protected_hardlinks | ||
36 | - protected_symlinks | ||
35 | - suid_dumpable | 37 | - suid_dumpable |
36 | - super-max | 38 | - super-max |
37 | - super-nr | 39 | - super-nr |
@@ -157,6 +159,46 @@ The default is 65534. | |||
157 | 159 | ||
158 | ============================================================== | 160 | ============================================================== |
159 | 161 | ||
162 | protected_hardlinks: | ||
163 | |||
164 | A long-standing class of security issues is the hardlink-based | ||
165 | time-of-check-time-of-use race, most commonly seen in world-writable | ||
166 | directories like /tmp. The common method of exploitation of this flaw | ||
167 | is to cross privilege boundaries when following a given hardlink (i.e. a | ||
168 | root process follows a hardlink created by another user). Additionally, | ||
169 | on systems without separated partitions, this stops unauthorized users | ||
170 | from "pinning" vulnerable setuid/setgid files against being upgraded by | ||
171 | the administrator, or linking to special files. | ||
172 | |||
173 | When set to "0", hardlink creation behavior is unrestricted. | ||
174 | |||
175 | When set to "1" hardlinks cannot be created by users if they do not | ||
176 | already own the source file, or do not have read/write access to it. | ||
177 | |||
178 | This protection is based on the restrictions in Openwall and grsecurity. | ||
179 | |||
180 | ============================================================== | ||
181 | |||
182 | protected_symlinks: | ||
183 | |||
184 | A long-standing class of security issues is the symlink-based | ||
185 | time-of-check-time-of-use race, most commonly seen in world-writable | ||
186 | directories like /tmp. The common method of exploitation of this flaw | ||
187 | is to cross privilege boundaries when following a given symlink (i.e. a | ||
188 | root process follows a symlink belonging to another user). For a likely | ||
189 | incomplete list of hundreds of examples across the years, please see: | ||
190 | http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=/tmp | ||
191 | |||
192 | When set to "0", symlink following behavior is unrestricted. | ||
193 | |||
194 | When set to "1" symlinks are permitted to be followed only when outside | ||
195 | a sticky world-writable directory, or when the uid of the symlink and | ||
196 | follower match, or when the directory owner matches the symlink's owner. | ||
197 | |||
198 | This protection is based on the restrictions in Openwall and grsecurity. | ||
199 | |||
200 | ============================================================== | ||
201 | |||
160 | suid_dumpable: | 202 | suid_dumpable: |
161 | 203 | ||
162 | This value can be used to query and set the core dump mode for setuid | 204 | This value can be used to query and set the core dump mode for setuid |
diff --git a/fs/namei.c b/fs/namei.c index afa087649ddb..3861d85f8488 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
@@ -650,6 +650,119 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki | |||
650 | path_put(link); | 650 | path_put(link); |
651 | } | 651 | } |
652 | 652 | ||
653 | int sysctl_protected_symlinks __read_mostly = 1; | ||
654 | int sysctl_protected_hardlinks __read_mostly = 1; | ||
655 | |||
656 | /** | ||
657 | * may_follow_link - Check symlink following for unsafe situations | ||
658 | * @link: The path of the symlink | ||
659 | * | ||
660 | * In the case of the sysctl_protected_symlinks sysctl being enabled, | ||
661 | * CAP_DAC_OVERRIDE needs to be specifically ignored if the symlink is | ||
662 | * in a sticky world-writable directory. This is to protect privileged | ||
663 | * processes from failing races against path names that may change out | ||
664 | * from under them by way of other users creating malicious symlinks. | ||
665 | * It will permit symlinks to be followed only when outside a sticky | ||
666 | * world-writable directory, or when the uid of the symlink and follower | ||
667 | * match, or when the directory owner matches the symlink's owner. | ||
668 | * | ||
669 | * Returns 0 if following the symlink is allowed, -ve on error. | ||
670 | */ | ||
671 | static inline int may_follow_link(struct path *link, struct nameidata *nd) | ||
672 | { | ||
673 | const struct inode *inode; | ||
674 | const struct inode *parent; | ||
675 | |||
676 | if (!sysctl_protected_symlinks) | ||
677 | return 0; | ||
678 | |||
679 | /* Allowed if owner and follower match. */ | ||
680 | inode = link->dentry->d_inode; | ||
681 | if (current_cred()->fsuid == inode->i_uid) | ||
682 | return 0; | ||
683 | |||
684 | /* Allowed if parent directory not sticky and world-writable. */ | ||
685 | parent = nd->path.dentry->d_inode; | ||
686 | if ((parent->i_mode & (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH)) | ||
687 | return 0; | ||
688 | |||
689 | /* Allowed if parent directory and link owner match. */ | ||
690 | if (parent->i_uid == inode->i_uid) | ||
691 | return 0; | ||
692 | |||
693 | path_put_conditional(link, nd); | ||
694 | path_put(&nd->path); | ||
695 | return -EACCES; | ||
696 | } | ||
697 | |||
698 | /** | ||
699 | * safe_hardlink_source - Check for safe hardlink conditions | ||
700 | * @inode: the source inode to hardlink from | ||
701 | * | ||
702 | * Return false if at least one of the following conditions: | ||
703 | * - inode is not a regular file | ||
704 | * - inode is setuid | ||
705 | * - inode is setgid and group-exec | ||
706 | * - access failure for read and write | ||
707 | * | ||
708 | * Otherwise returns true. | ||
709 | */ | ||
710 | static bool safe_hardlink_source(struct inode *inode) | ||
711 | { | ||
712 | umode_t mode = inode->i_mode; | ||
713 | |||
714 | /* Special files should not get pinned to the filesystem. */ | ||
715 | if (!S_ISREG(mode)) | ||
716 | return false; | ||
717 | |||
718 | /* Setuid files should not get pinned to the filesystem. */ | ||
719 | if (mode & S_ISUID) | ||
720 | return false; | ||
721 | |||
722 | /* Executable setgid files should not get pinned to the filesystem. */ | ||
723 | if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) | ||
724 | return false; | ||
725 | |||
726 | /* Hardlinking to unreadable or unwritable sources is dangerous. */ | ||
727 | if (inode_permission(inode, MAY_READ | MAY_WRITE)) | ||
728 | return false; | ||
729 | |||
730 | return true; | ||
731 | } | ||
732 | |||
733 | /** | ||
734 | * may_linkat - Check permissions for creating a hardlink | ||
735 | * @link: the source to hardlink from | ||
736 | * | ||
737 | * Block hardlink when all of: | ||
738 | * - sysctl_protected_hardlinks enabled | ||
739 | * - fsuid does not match inode | ||
740 | * - hardlink source is unsafe (see safe_hardlink_source() above) | ||
741 | * - not CAP_FOWNER | ||
742 | * | ||
743 | * Returns 0 if successful, -ve on error. | ||
744 | */ | ||
745 | static int may_linkat(struct path *link) | ||
746 | { | ||
747 | const struct cred *cred; | ||
748 | struct inode *inode; | ||
749 | |||
750 | if (!sysctl_protected_hardlinks) | ||
751 | return 0; | ||
752 | |||
753 | cred = current_cred(); | ||
754 | inode = link->dentry->d_inode; | ||
755 | |||
756 | /* Source inode owner (or CAP_FOWNER) can hardlink all they like, | ||
757 | * otherwise, it must be a safe source. | ||
758 | */ | ||
759 | if (cred->fsuid == inode->i_uid || safe_hardlink_source(inode) || | ||
760 | capable(CAP_FOWNER)) | ||
761 | return 0; | ||
762 | |||
763 | return -EPERM; | ||
764 | } | ||
765 | |||
653 | static __always_inline int | 766 | static __always_inline int |
654 | follow_link(struct path *link, struct nameidata *nd, void **p) | 767 | follow_link(struct path *link, struct nameidata *nd, void **p) |
655 | { | 768 | { |
@@ -1818,6 +1931,9 @@ static int path_lookupat(int dfd, const char *name, | |||
1818 | while (err > 0) { | 1931 | while (err > 0) { |
1819 | void *cookie; | 1932 | void *cookie; |
1820 | struct path link = path; | 1933 | struct path link = path; |
1934 | err = may_follow_link(&link, nd); | ||
1935 | if (unlikely(err)) | ||
1936 | break; | ||
1821 | nd->flags |= LOOKUP_PARENT; | 1937 | nd->flags |= LOOKUP_PARENT; |
1822 | err = follow_link(&link, nd, &cookie); | 1938 | err = follow_link(&link, nd, &cookie); |
1823 | if (err) | 1939 | if (err) |
@@ -2778,6 +2894,9 @@ static struct file *path_openat(int dfd, const char *pathname, | |||
2778 | error = -ELOOP; | 2894 | error = -ELOOP; |
2779 | break; | 2895 | break; |
2780 | } | 2896 | } |
2897 | error = may_follow_link(&link, nd); | ||
2898 | if (unlikely(error)) | ||
2899 | break; | ||
2781 | nd->flags |= LOOKUP_PARENT; | 2900 | nd->flags |= LOOKUP_PARENT; |
2782 | nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); | 2901 | nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); |
2783 | error = follow_link(&link, nd, &cookie); | 2902 | error = follow_link(&link, nd, &cookie); |
@@ -3421,6 +3540,9 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname, | |||
3421 | error = -EXDEV; | 3540 | error = -EXDEV; |
3422 | if (old_path.mnt != new_path.mnt) | 3541 | if (old_path.mnt != new_path.mnt) |
3423 | goto out_dput; | 3542 | goto out_dput; |
3543 | error = may_linkat(&old_path); | ||
3544 | if (unlikely(error)) | ||
3545 | goto out_dput; | ||
3424 | error = security_path_link(old_path.dentry, &new_path, new_dentry); | 3546 | error = security_path_link(old_path.dentry, &new_path, new_dentry); |
3425 | if (error) | 3547 | if (error) |
3426 | goto out_dput; | 3548 | goto out_dput; |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 478237844648..80c819cbe272 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -437,6 +437,8 @@ extern unsigned long get_max_files(void); | |||
437 | extern int sysctl_nr_open; | 437 | extern int sysctl_nr_open; |
438 | extern struct inodes_stat_t inodes_stat; | 438 | extern struct inodes_stat_t inodes_stat; |
439 | extern int leases_enable, lease_break_time; | 439 | extern int leases_enable, lease_break_time; |
440 | extern int sysctl_protected_symlinks; | ||
441 | extern int sysctl_protected_hardlinks; | ||
440 | 442 | ||
441 | struct buffer_head; | 443 | struct buffer_head; |
442 | typedef int (get_block_t)(struct inode *inode, sector_t iblock, | 444 | typedef int (get_block_t)(struct inode *inode, sector_t iblock, |
diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 4ab11879aeb4..5d9a1d2b27b4 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c | |||
@@ -1494,6 +1494,24 @@ static struct ctl_table fs_table[] = { | |||
1494 | #endif | 1494 | #endif |
1495 | #endif | 1495 | #endif |
1496 | { | 1496 | { |
1497 | .procname = "protected_symlinks", | ||
1498 | .data = &sysctl_protected_symlinks, | ||
1499 | .maxlen = sizeof(int), | ||
1500 | .mode = 0600, | ||
1501 | .proc_handler = proc_dointvec_minmax, | ||
1502 | .extra1 = &zero, | ||
1503 | .extra2 = &one, | ||
1504 | }, | ||
1505 | { | ||
1506 | .procname = "protected_hardlinks", | ||
1507 | .data = &sysctl_protected_hardlinks, | ||
1508 | .maxlen = sizeof(int), | ||
1509 | .mode = 0600, | ||
1510 | .proc_handler = proc_dointvec_minmax, | ||
1511 | .extra1 = &zero, | ||
1512 | .extra2 = &one, | ||
1513 | }, | ||
1514 | { | ||
1497 | .procname = "suid_dumpable", | 1515 | .procname = "suid_dumpable", |
1498 | .data = &suid_dumpable, | 1516 | .data = &suid_dumpable, |
1499 | .maxlen = sizeof(int), | 1517 | .maxlen = sizeof(int), |