xorl %eax, %eax

GRKERNSEC_LINK Linking Restrictions

leave a comment »

Almost every exploit developer has coded at least an exploit for common TOCTTOU conditions using symbolic attack. It’s one of the classics of race conditions. Other exploits allowed manipulation of files using hardlinks which are really handy.
To prevent all these, grsecurity implements a protection under ‘GRKERNSEC_LINK’ configuration option.

	bool "Linking restrictions"
	  If you say Y here, /tmp race exploits will be prevented, since users
	  will no longer be able to follow symlinks owned by other users in
	  world-writable +t directories (i.e. /tmp), unless the owner of the
	  symlink is the owner of the directory. users will also not be
	  able to hardlink to files they do not own.  If the sysctl option is
	  enabled, a sysctl option with name "linking_restrictions" is created.

The description is complete so I will move directly to the code that implements that feature. That code is placed at grsecurity/grsec_link.c and includes the following function.

gr_handle_follow_link(const struct inode *parent,
		      const struct inode *inode,
		      const struct dentry *dentry, const struct vfsmount *mnt)
	const struct cred *cred = current_cred();

	if (grsec_enable_link && S_ISLNK(inode->i_mode) &&
	    (parent->i_mode & S_ISVTX) && (parent->i_uid != inode->i_uid) &&
	    (parent->i_mode & S_IWOTH) && (cred->fsuid != inode->i_uid)) {
		gr_log_fs_int2(GR_DONT_AUDIT, GR_SYMLINK_MSG, dentry, mnt, inode->i_uid, inode->i_gid);
		return -EACCES;
	return 0;

So, gr_handle_follow_link() shown above will perform the following checks for the given link:
– Check if ‘grsec_enable_link’ is set
– The given inode is actually a link
– Check that the parent is a directory with sticky bit S_ISVTX
– The parent’s owner’s user ID is different from the link’s one
– Check that parent is writable by other users
– Check that link’s file system access user ID is different from the link’s one
These checks combined lead to a link file that is placed in a world writable directory and point to a file with different user ID. If all of the previous statements are true, then the code will log the activity using gr_log_fs_int2() and return with ‘-EACCES’ (Access Denied) error code. Finally, this code is inserted in fs/namei.c and specifically at do_follow_link() Linux kernel’s function to perform that check…

 * This limits recursive symlink follows to 8, while
 * limiting consecutive symlinks to 40.
 * Without that kind of total limit, nasty chains of consecutive
 * symlinks can cause almost arbitrarily long lookups. 
static inline int do_follow_link(struct path *path, struct nameidata *nd)
        void *cookie;
        int err = -ELOOP;
        if (current->link_count >= MAX_NESTED_LINKS)
                goto loop;
        if (current->total_link_count >= 40)
                goto loop;
        BUG_ON(nd->depth >= MAX_NESTED_LINKS);
        err = security_inode_follow_link(path->dentry, nd);
        if (err)
 		goto loop;

	if (gr_handle_follow_link(path->dentry->d_parent->d_inode,
				  path->dentry->d_inode, path->dentry, nd->path.mnt)) {
		err = -EACCES;
		goto loop;

        return err;

exactly after the link count check is performed. However, grsecurity provides similar protection for hardlink attacks too. The equivalent hardlink routine is available back at the grsecurity/grsec_link.c file.

gr_handle_hardlink(const struct dentry *dentry,
		   const struct vfsmount *mnt,
		   struct inode *inode, const int mode, const char *to)
	const struct cred *cred = current_cred();

	if (grsec_enable_link && cred->fsuid != inode->i_uid &&
	    (!S_ISREG(mode) || (mode & S_ISUID) ||
	     ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) ||
	     (generic_permission(inode, MAY_READ | MAY_WRITE, NULL))) &&
	    !capable(CAP_FOWNER) && cred->uid) {
		gr_log_fs_int2_str(GR_DONT_AUDIT, GR_HARDLINK_MSG, dentry, mnt, inode->i_uid, inode->i_gid, to);
		return -EPERM;
	return 0;

Once again, based on the given file, the check will make sure that ‘grsec_enable_link’ is set and link’s file access user ID is different from the inode’s one. Also, it requires that the ‘CAP_FOWNER’ (Allow all file operations if file’s Owner ID is the same as User ID) POSIX capability is not set and the user ID is not that of root. Assuming that the previous statements are true, then any of the following:
– If it’s a regular file
– If it’s a SUID binary
– If it’s a SGID executable binary
– If the inode is readable and writable
will trigger the logging through gr_log_fs_int2_str() and return with ‘-EPERM’ (Permission Denied). This means that users cannot create hardlinks of files they do not own. This function is used inside linkat(2) system call which can be found at fs/namei.c in the Linux kernel’s source code tree like this:

 * Hardlinks are often used in delicate situations.  We avoid
 * security-related surprises by not following symlinks on the
 * newname.  --KAB
 * We don't follow them on the oldname either to be compatible
 * with linux 2.0, and to avoid hard-linking to directories
 * and other special files.  --ADM
SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
                int, newdfd, const char __user *, newname, int, flags)
        struct dentry *new_dentry;
        struct nameidata nd;
        struct path old_path;
        int error;
        char *to;
	if (gr_handle_hardlink(old_path.dentry, old_path.mnt,
			       old_path.dentry->d_inode->i_mode, to)) {
		error = -EACCES;
		goto out_dput;

	if (!gr_acl_handle_link(new_dentry, nd.path.dentry, nd.path.mnt,
				old_path.dentry, old_path.mnt, to)) {
		error = -EACCES;
		goto out_dput;

 	error = mnt_want_write(nd.path.mnt);
        return error;

Here you can read that apart from the previously discussed gr_handle_hardlink() there is another check using gr_acl_handle_link(). That’s an access list function from grsecurity/gracl_fs.c that is used by the RBAC system of grsecurity. Since RBAC system is a complex project and it’s not affected by the discussed configuration option I’ll talk about it in a different, dedicated post.

Written by xorl

November 11, 2010 at 21:15

Posted in grsecurity, linux, security

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: