1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * AppArmor security module
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * This file contains AppArmor function for pathnames
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Copyright (C) 1998-2008 Novell/SUSE
8*4882a593Smuzhiyun * Copyright 2009-2010 Canonical Ltd.
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #include <linux/magic.h>
12*4882a593Smuzhiyun #include <linux/mount.h>
13*4882a593Smuzhiyun #include <linux/namei.h>
14*4882a593Smuzhiyun #include <linux/nsproxy.h>
15*4882a593Smuzhiyun #include <linux/path.h>
16*4882a593Smuzhiyun #include <linux/sched.h>
17*4882a593Smuzhiyun #include <linux/slab.h>
18*4882a593Smuzhiyun #include <linux/fs_struct.h>
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #include "include/apparmor.h"
21*4882a593Smuzhiyun #include "include/path.h"
22*4882a593Smuzhiyun #include "include/policy.h"
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun /* modified from dcache.c */
prepend(char ** buffer,int buflen,const char * str,int namelen)25*4882a593Smuzhiyun static int prepend(char **buffer, int buflen, const char *str, int namelen)
26*4882a593Smuzhiyun {
27*4882a593Smuzhiyun buflen -= namelen;
28*4882a593Smuzhiyun if (buflen < 0)
29*4882a593Smuzhiyun return -ENAMETOOLONG;
30*4882a593Smuzhiyun *buffer -= namelen;
31*4882a593Smuzhiyun memcpy(*buffer, str, namelen);
32*4882a593Smuzhiyun return 0;
33*4882a593Smuzhiyun }
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun #define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun /* If the path is not connected to the expected root,
38*4882a593Smuzhiyun * check if it is a sysctl and handle specially else remove any
39*4882a593Smuzhiyun * leading / that __d_path may have returned.
40*4882a593Smuzhiyun * Unless
41*4882a593Smuzhiyun * specifically directed to connect the path,
42*4882a593Smuzhiyun * OR
43*4882a593Smuzhiyun * if in a chroot and doing chroot relative paths and the path
44*4882a593Smuzhiyun * resolves to the namespace root (would be connected outside
45*4882a593Smuzhiyun * of chroot) and specifically directed to connect paths to
46*4882a593Smuzhiyun * namespace root.
47*4882a593Smuzhiyun */
disconnect(const struct path * path,char * buf,char ** name,int flags,const char * disconnected)48*4882a593Smuzhiyun static int disconnect(const struct path *path, char *buf, char **name,
49*4882a593Smuzhiyun int flags, const char *disconnected)
50*4882a593Smuzhiyun {
51*4882a593Smuzhiyun int error = 0;
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun if (!(flags & PATH_CONNECT_PATH) &&
54*4882a593Smuzhiyun !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
55*4882a593Smuzhiyun our_mnt(path->mnt))) {
56*4882a593Smuzhiyun /* disconnected path, don't return pathname starting
57*4882a593Smuzhiyun * with '/'
58*4882a593Smuzhiyun */
59*4882a593Smuzhiyun error = -EACCES;
60*4882a593Smuzhiyun if (**name == '/')
61*4882a593Smuzhiyun *name = *name + 1;
62*4882a593Smuzhiyun } else {
63*4882a593Smuzhiyun if (**name != '/')
64*4882a593Smuzhiyun /* CONNECT_PATH with missing root */
65*4882a593Smuzhiyun error = prepend(name, *name - buf, "/", 1);
66*4882a593Smuzhiyun if (!error && disconnected)
67*4882a593Smuzhiyun error = prepend(name, *name - buf, disconnected,
68*4882a593Smuzhiyun strlen(disconnected));
69*4882a593Smuzhiyun }
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun return error;
72*4882a593Smuzhiyun }
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun /**
75*4882a593Smuzhiyun * d_namespace_path - lookup a name associated with a given path
76*4882a593Smuzhiyun * @path: path to lookup (NOT NULL)
77*4882a593Smuzhiyun * @buf: buffer to store path to (NOT NULL)
78*4882a593Smuzhiyun * @name: Returns - pointer for start of path name with in @buf (NOT NULL)
79*4882a593Smuzhiyun * @flags: flags controlling path lookup
80*4882a593Smuzhiyun * @disconnected: string to prefix to disconnected paths
81*4882a593Smuzhiyun *
82*4882a593Smuzhiyun * Handle path name lookup.
83*4882a593Smuzhiyun *
84*4882a593Smuzhiyun * Returns: %0 else error code if path lookup fails
85*4882a593Smuzhiyun * When no error the path name is returned in @name which points to
86*4882a593Smuzhiyun * to a position in @buf
87*4882a593Smuzhiyun */
d_namespace_path(const struct path * path,char * buf,char ** name,int flags,const char * disconnected)88*4882a593Smuzhiyun static int d_namespace_path(const struct path *path, char *buf, char **name,
89*4882a593Smuzhiyun int flags, const char *disconnected)
90*4882a593Smuzhiyun {
91*4882a593Smuzhiyun char *res;
92*4882a593Smuzhiyun int error = 0;
93*4882a593Smuzhiyun int connected = 1;
94*4882a593Smuzhiyun int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
95*4882a593Smuzhiyun int buflen = aa_g_path_max - isdir;
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun if (path->mnt->mnt_flags & MNT_INTERNAL) {
98*4882a593Smuzhiyun /* it's not mounted anywhere */
99*4882a593Smuzhiyun res = dentry_path(path->dentry, buf, buflen);
100*4882a593Smuzhiyun *name = res;
101*4882a593Smuzhiyun if (IS_ERR(res)) {
102*4882a593Smuzhiyun *name = buf;
103*4882a593Smuzhiyun return PTR_ERR(res);
104*4882a593Smuzhiyun }
105*4882a593Smuzhiyun if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
106*4882a593Smuzhiyun strncmp(*name, "/sys/", 5) == 0) {
107*4882a593Smuzhiyun /* TODO: convert over to using a per namespace
108*4882a593Smuzhiyun * control instead of hard coded /proc
109*4882a593Smuzhiyun */
110*4882a593Smuzhiyun error = prepend(name, *name - buf, "/proc", 5);
111*4882a593Smuzhiyun goto out;
112*4882a593Smuzhiyun } else
113*4882a593Smuzhiyun error = disconnect(path, buf, name, flags,
114*4882a593Smuzhiyun disconnected);
115*4882a593Smuzhiyun goto out;
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun /* resolve paths relative to chroot?*/
119*4882a593Smuzhiyun if (flags & PATH_CHROOT_REL) {
120*4882a593Smuzhiyun struct path root;
121*4882a593Smuzhiyun get_fs_root(current->fs, &root);
122*4882a593Smuzhiyun res = __d_path(path, &root, buf, buflen);
123*4882a593Smuzhiyun path_put(&root);
124*4882a593Smuzhiyun } else {
125*4882a593Smuzhiyun res = d_absolute_path(path, buf, buflen);
126*4882a593Smuzhiyun if (!our_mnt(path->mnt))
127*4882a593Smuzhiyun connected = 0;
128*4882a593Smuzhiyun }
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun /* handle error conditions - and still allow a partial path to
131*4882a593Smuzhiyun * be returned.
132*4882a593Smuzhiyun */
133*4882a593Smuzhiyun if (!res || IS_ERR(res)) {
134*4882a593Smuzhiyun if (PTR_ERR(res) == -ENAMETOOLONG) {
135*4882a593Smuzhiyun error = -ENAMETOOLONG;
136*4882a593Smuzhiyun *name = buf;
137*4882a593Smuzhiyun goto out;
138*4882a593Smuzhiyun }
139*4882a593Smuzhiyun connected = 0;
140*4882a593Smuzhiyun res = dentry_path_raw(path->dentry, buf, buflen);
141*4882a593Smuzhiyun if (IS_ERR(res)) {
142*4882a593Smuzhiyun error = PTR_ERR(res);
143*4882a593Smuzhiyun *name = buf;
144*4882a593Smuzhiyun goto out;
145*4882a593Smuzhiyun }
146*4882a593Smuzhiyun } else if (!our_mnt(path->mnt))
147*4882a593Smuzhiyun connected = 0;
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun *name = res;
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun if (!connected)
152*4882a593Smuzhiyun error = disconnect(path, buf, name, flags, disconnected);
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun /* Handle two cases:
155*4882a593Smuzhiyun * 1. A deleted dentry && profile is not allowing mediation of deleted
156*4882a593Smuzhiyun * 2. On some filesystems, newly allocated dentries appear to the
157*4882a593Smuzhiyun * security_path hooks as a deleted dentry except without an inode
158*4882a593Smuzhiyun * allocated.
159*4882a593Smuzhiyun */
160*4882a593Smuzhiyun if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
161*4882a593Smuzhiyun !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
162*4882a593Smuzhiyun error = -ENOENT;
163*4882a593Smuzhiyun goto out;
164*4882a593Smuzhiyun }
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun out:
167*4882a593Smuzhiyun /*
168*4882a593Smuzhiyun * Append "/" to the pathname. The root directory is a special
169*4882a593Smuzhiyun * case; it already ends in slash.
170*4882a593Smuzhiyun */
171*4882a593Smuzhiyun if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
172*4882a593Smuzhiyun strcpy(&buf[aa_g_path_max - 2], "/");
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun return error;
175*4882a593Smuzhiyun }
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun /**
178*4882a593Smuzhiyun * aa_path_name - get the pathname to a buffer ensure dir / is appended
179*4882a593Smuzhiyun * @path: path the file (NOT NULL)
180*4882a593Smuzhiyun * @flags: flags controlling path name generation
181*4882a593Smuzhiyun * @buffer: buffer to put name in (NOT NULL)
182*4882a593Smuzhiyun * @name: Returns - the generated path name if !error (NOT NULL)
183*4882a593Smuzhiyun * @info: Returns - information on why the path lookup failed (MAYBE NULL)
184*4882a593Smuzhiyun * @disconnected: string to prepend to disconnected paths
185*4882a593Smuzhiyun *
186*4882a593Smuzhiyun * @name is a pointer to the beginning of the pathname (which usually differs
187*4882a593Smuzhiyun * from the beginning of the buffer), or NULL. If there is an error @name
188*4882a593Smuzhiyun * may contain a partial or invalid name that can be used for audit purposes,
189*4882a593Smuzhiyun * but it can not be used for mediation.
190*4882a593Smuzhiyun *
191*4882a593Smuzhiyun * We need PATH_IS_DIR to indicate whether the file is a directory or not
192*4882a593Smuzhiyun * because the file may not yet exist, and so we cannot check the inode's
193*4882a593Smuzhiyun * file type.
194*4882a593Smuzhiyun *
195*4882a593Smuzhiyun * Returns: %0 else error code if could retrieve name
196*4882a593Smuzhiyun */
aa_path_name(const struct path * path,int flags,char * buffer,const char ** name,const char ** info,const char * disconnected)197*4882a593Smuzhiyun int aa_path_name(const struct path *path, int flags, char *buffer,
198*4882a593Smuzhiyun const char **name, const char **info, const char *disconnected)
199*4882a593Smuzhiyun {
200*4882a593Smuzhiyun char *str = NULL;
201*4882a593Smuzhiyun int error = d_namespace_path(path, buffer, &str, flags, disconnected);
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun if (info && error) {
204*4882a593Smuzhiyun if (error == -ENOENT)
205*4882a593Smuzhiyun *info = "Failed name lookup - deleted entry";
206*4882a593Smuzhiyun else if (error == -EACCES)
207*4882a593Smuzhiyun *info = "Failed name lookup - disconnected path";
208*4882a593Smuzhiyun else if (error == -ENAMETOOLONG)
209*4882a593Smuzhiyun *info = "Failed name lookup - name too long";
210*4882a593Smuzhiyun else
211*4882a593Smuzhiyun *info = "Failed name lookup";
212*4882a593Smuzhiyun }
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun *name = str;
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun return error;
217*4882a593Smuzhiyun }
218