xref: /linux/security/apparmor/path.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
1b886d83cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2cdff2642SJohn Johansen /*
3cdff2642SJohn Johansen  * AppArmor security module
4cdff2642SJohn Johansen  *
5cdff2642SJohn Johansen  * This file contains AppArmor function for pathnames
6cdff2642SJohn Johansen  *
7cdff2642SJohn Johansen  * Copyright (C) 1998-2008 Novell/SUSE
8cdff2642SJohn Johansen  * Copyright 2009-2010 Canonical Ltd.
9cdff2642SJohn Johansen  */
10cdff2642SJohn Johansen 
11cdff2642SJohn Johansen #include <linux/magic.h>
12cdff2642SJohn Johansen #include <linux/mount.h>
13cdff2642SJohn Johansen #include <linux/namei.h>
14cdff2642SJohn Johansen #include <linux/nsproxy.h>
15cdff2642SJohn Johansen #include <linux/path.h>
16cdff2642SJohn Johansen #include <linux/sched.h>
17cdff2642SJohn Johansen #include <linux/slab.h>
18cdff2642SJohn Johansen #include <linux/fs_struct.h>
19cdff2642SJohn Johansen 
20cdff2642SJohn Johansen #include "include/apparmor.h"
21cdff2642SJohn Johansen #include "include/path.h"
22cdff2642SJohn Johansen #include "include/policy.h"
23cdff2642SJohn Johansen 
24cdff2642SJohn Johansen /* modified from dcache.c */
prepend(char ** buffer,int buflen,const char * str,int namelen)25cdff2642SJohn Johansen static int prepend(char **buffer, int buflen, const char *str, int namelen)
26cdff2642SJohn Johansen {
27cdff2642SJohn Johansen 	buflen -= namelen;
28cdff2642SJohn Johansen 	if (buflen < 0)
29cdff2642SJohn Johansen 		return -ENAMETOOLONG;
30cdff2642SJohn Johansen 	*buffer -= namelen;
31cdff2642SJohn Johansen 	memcpy(*buffer, str, namelen);
32cdff2642SJohn Johansen 	return 0;
33cdff2642SJohn Johansen }
34cdff2642SJohn Johansen 
35cdff2642SJohn Johansen #define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
36cdff2642SJohn Johansen 
37bd35db8bSJohn Johansen /* If the path is not connected to the expected root,
38bd35db8bSJohn Johansen  * check if it is a sysctl and handle specially else remove any
39bd35db8bSJohn Johansen  * leading / that __d_path may have returned.
40bd35db8bSJohn Johansen  * Unless
41bd35db8bSJohn Johansen  *     specifically directed to connect the path,
42bd35db8bSJohn Johansen  * OR
43bd35db8bSJohn Johansen  *     if in a chroot and doing chroot relative paths and the path
44bd35db8bSJohn Johansen  *     resolves to the namespace root (would be connected outside
45bd35db8bSJohn Johansen  *     of chroot) and specifically directed to connect paths to
46bd35db8bSJohn Johansen  *     namespace root.
47bd35db8bSJohn Johansen  */
disconnect(const struct path * path,char * buf,char ** name,int flags,const char * disconnected)48bd35db8bSJohn Johansen static int disconnect(const struct path *path, char *buf, char **name,
4972c8a768SJohn Johansen 		      int flags, const char *disconnected)
50bd35db8bSJohn Johansen {
51bd35db8bSJohn Johansen 	int error = 0;
52bd35db8bSJohn Johansen 
53bd35db8bSJohn Johansen 	if (!(flags & PATH_CONNECT_PATH) &&
54bd35db8bSJohn Johansen 	    !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
55bd35db8bSJohn Johansen 	      our_mnt(path->mnt))) {
56bd35db8bSJohn Johansen 		/* disconnected path, don't return pathname starting
57bd35db8bSJohn Johansen 		 * with '/'
58bd35db8bSJohn Johansen 		 */
59bd35db8bSJohn Johansen 		error = -EACCES;
60bd35db8bSJohn Johansen 		if (**name == '/')
61bd35db8bSJohn Johansen 			*name = *name + 1;
6272c8a768SJohn Johansen 	} else {
6372c8a768SJohn Johansen 		if (**name != '/')
64bd35db8bSJohn Johansen 			/* CONNECT_PATH with missing root */
65bd35db8bSJohn Johansen 			error = prepend(name, *name - buf, "/", 1);
6672c8a768SJohn Johansen 		if (!error && disconnected)
6772c8a768SJohn Johansen 			error = prepend(name, *name - buf, disconnected,
6872c8a768SJohn Johansen 					strlen(disconnected));
6972c8a768SJohn Johansen 	}
70bd35db8bSJohn Johansen 
71bd35db8bSJohn Johansen 	return error;
72bd35db8bSJohn Johansen }
73bd35db8bSJohn Johansen 
74cdff2642SJohn Johansen /**
75cdff2642SJohn Johansen  * d_namespace_path - lookup a name associated with a given path
76cdff2642SJohn Johansen  * @path: path to lookup  (NOT NULL)
77cdff2642SJohn Johansen  * @buf:  buffer to store path to  (NOT NULL)
78cdff2642SJohn Johansen  * @name: Returns - pointer for start of path name with in @buf (NOT NULL)
79cdff2642SJohn Johansen  * @flags: flags controlling path lookup
8072c8a768SJohn Johansen  * @disconnected: string to prefix to disconnected paths
81cdff2642SJohn Johansen  *
82cdff2642SJohn Johansen  * Handle path name lookup.
83cdff2642SJohn Johansen  *
84cdff2642SJohn Johansen  * Returns: %0 else error code if path lookup fails
85cdff2642SJohn Johansen  *          When no error the path name is returned in @name which points to
86*4af7c863SRandy Dunlap  *          a position in @buf
87cdff2642SJohn Johansen  */
d_namespace_path(const struct path * path,char * buf,char ** name,int flags,const char * disconnected)884227c333SJohn Johansen static int d_namespace_path(const struct path *path, char *buf, char **name,
894227c333SJohn Johansen 			    int flags, const char *disconnected)
90cdff2642SJohn Johansen {
91cdff2642SJohn Johansen 	char *res;
9202125a82SAl Viro 	int error = 0;
9302125a82SAl Viro 	int connected = 1;
944227c333SJohn Johansen 	int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
954227c333SJohn Johansen 	int buflen = aa_g_path_max - isdir;
96cdff2642SJohn Johansen 
9702125a82SAl Viro 	if (path->mnt->mnt_flags & MNT_INTERNAL) {
9802125a82SAl Viro 		/* it's not mounted anywhere */
9902125a82SAl Viro 		res = dentry_path(path->dentry, buf, buflen);
10002125a82SAl Viro 		*name = res;
10102125a82SAl Viro 		if (IS_ERR(res)) {
10202125a82SAl Viro 			*name = buf;
10302125a82SAl Viro 			return PTR_ERR(res);
10402125a82SAl Viro 		}
10502125a82SAl Viro 		if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
10602125a82SAl Viro 		    strncmp(*name, "/sys/", 5) == 0) {
10702125a82SAl Viro 			/* TODO: convert over to using a per namespace
10802125a82SAl Viro 			 * control instead of hard coded /proc
10902125a82SAl Viro 			 */
1104227c333SJohn Johansen 			error = prepend(name, *name - buf, "/proc", 5);
1114227c333SJohn Johansen 			goto out;
112bd35db8bSJohn Johansen 		} else
1134227c333SJohn Johansen 			error = disconnect(path, buf, name, flags,
11472c8a768SJohn Johansen 					   disconnected);
1154227c333SJohn Johansen 		goto out;
116cdff2642SJohn Johansen 	}
117cdff2642SJohn Johansen 
11802125a82SAl Viro 	/* resolve paths relative to chroot?*/
11902125a82SAl Viro 	if (flags & PATH_CHROOT_REL) {
12002125a82SAl Viro 		struct path root;
12102125a82SAl Viro 		get_fs_root(current->fs, &root);
12202125a82SAl Viro 		res = __d_path(path, &root, buf, buflen);
12302125a82SAl Viro 		path_put(&root);
1243372b68aSJohn Johansen 	} else {
12502125a82SAl Viro 		res = d_absolute_path(path, buf, buflen);
1263372b68aSJohn Johansen 		if (!our_mnt(path->mnt))
1273372b68aSJohn Johansen 			connected = 0;
1283372b68aSJohn Johansen 	}
129cdff2642SJohn Johansen 
130cdff2642SJohn Johansen 	/* handle error conditions - and still allow a partial path to
131cdff2642SJohn Johansen 	 * be returned.
132cdff2642SJohn Johansen 	 */
1333372b68aSJohn Johansen 	if (!res || IS_ERR(res)) {
1344227c333SJohn Johansen 		if (PTR_ERR(res) == -ENAMETOOLONG) {
1354227c333SJohn Johansen 			error = -ENAMETOOLONG;
1364227c333SJohn Johansen 			*name = buf;
1374227c333SJohn Johansen 			goto out;
1384227c333SJohn Johansen 		}
1393372b68aSJohn Johansen 		connected = 0;
140fbba8d89SJohn Johansen 		res = dentry_path_raw(path->dentry, buf, buflen);
141fbba8d89SJohn Johansen 		if (IS_ERR(res)) {
142cdff2642SJohn Johansen 			error = PTR_ERR(res);
143cdff2642SJohn Johansen 			*name = buf;
144cdff2642SJohn Johansen 			goto out;
145e4f4e6baSVasyl Gomonovych 		}
146fbba8d89SJohn Johansen 	} else if (!our_mnt(path->mnt))
14702125a82SAl Viro 		connected = 0;
148cdff2642SJohn Johansen 
149fbba8d89SJohn Johansen 	*name = res;
150fbba8d89SJohn Johansen 
1514227c333SJohn Johansen 	if (!connected)
1524227c333SJohn Johansen 		error = disconnect(path, buf, name, flags, disconnected);
1534227c333SJohn Johansen 
154e819ff51SJohn Johansen 	/* Handle two cases:
155e819ff51SJohn Johansen 	 * 1. A deleted dentry && profile is not allowing mediation of deleted
156e819ff51SJohn Johansen 	 * 2. On some filesystems, newly allocated dentries appear to the
157e819ff51SJohn Johansen 	 *    security_path hooks as a deleted dentry except without an inode
158e819ff51SJohn Johansen 	 *    allocated.
159e819ff51SJohn Johansen 	 */
160729b8a3dSDavid Howells 	if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
1614227c333SJohn Johansen 	    !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
162cdff2642SJohn Johansen 			error = -ENOENT;
163cdff2642SJohn Johansen 			goto out;
164cdff2642SJohn Johansen 	}
165cdff2642SJohn Johansen 
166cdff2642SJohn Johansen out:
167cdff2642SJohn Johansen 	/*
168cdff2642SJohn Johansen 	 * Append "/" to the pathname.  The root directory is a special
169cdff2642SJohn Johansen 	 * case; it already ends in slash.
170cdff2642SJohn Johansen 	 */
1714227c333SJohn Johansen 	if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
1724227c333SJohn Johansen 		strcpy(&buf[aa_g_path_max - 2], "/");
17357fa1e18SJohn Johansen 
174cdff2642SJohn Johansen 	return error;
175cdff2642SJohn Johansen }
176cdff2642SJohn Johansen 
177cdff2642SJohn Johansen /**
1784227c333SJohn Johansen  * aa_path_name - get the pathname to a buffer ensure dir / is appended
179cdff2642SJohn Johansen  * @path: path the file  (NOT NULL)
180cdff2642SJohn Johansen  * @flags: flags controlling path name generation
1814227c333SJohn Johansen  * @buffer: buffer to put name in (NOT NULL)
182cdff2642SJohn Johansen  * @name: Returns - the generated path name if !error (NOT NULL)
18357fa1e18SJohn Johansen  * @info: Returns - information on why the path lookup failed (MAYBE NULL)
18472c8a768SJohn Johansen  * @disconnected: string to prepend to disconnected paths
185cdff2642SJohn Johansen  *
186cdff2642SJohn Johansen  * @name is a pointer to the beginning of the pathname (which usually differs
187cdff2642SJohn Johansen  * from the beginning of the buffer), or NULL.  If there is an error @name
188cdff2642SJohn Johansen  * may contain a partial or invalid name that can be used for audit purposes,
189cdff2642SJohn Johansen  * but it can not be used for mediation.
190cdff2642SJohn Johansen  *
191cdff2642SJohn Johansen  * We need PATH_IS_DIR to indicate whether the file is a directory or not
192cdff2642SJohn Johansen  * because the file may not yet exist, and so we cannot check the inode's
193cdff2642SJohn Johansen  * file type.
194cdff2642SJohn Johansen  *
195cdff2642SJohn Johansen  * Returns: %0 else error code if could retrieve name
196cdff2642SJohn Johansen  */
aa_path_name(const struct path * path,int flags,char * buffer,const char ** name,const char ** info,const char * disconnected)1974227c333SJohn Johansen int aa_path_name(const struct path *path, int flags, char *buffer,
19872c8a768SJohn Johansen 		 const char **name, const char **info, const char *disconnected)
199cdff2642SJohn Johansen {
2004227c333SJohn Johansen 	char *str = NULL;
2014227c333SJohn Johansen 	int error = d_namespace_path(path, buffer, &str, flags, disconnected);
202cdff2642SJohn Johansen 
2034227c333SJohn Johansen 	if (info && error) {
2044227c333SJohn Johansen 		if (error == -ENOENT)
2054227c333SJohn Johansen 			*info = "Failed name lookup - deleted entry";
2064227c333SJohn Johansen 		else if (error == -EACCES)
2074227c333SJohn Johansen 			*info = "Failed name lookup - disconnected path";
2084227c333SJohn Johansen 		else if (error == -ENAMETOOLONG)
2094227c333SJohn Johansen 			*info = "Failed name lookup - name too long";
2104227c333SJohn Johansen 		else
2114227c333SJohn Johansen 			*info = "Failed name lookup";
212cdff2642SJohn Johansen 	}
2134227c333SJohn Johansen 
214cdff2642SJohn Johansen 	*name = str;
215cdff2642SJohn Johansen 
216cdff2642SJohn Johansen 	return error;
217cdff2642SJohn Johansen }
218