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