linux/fs/dcookies.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * dcookies.c
   4 *
   5 * Copyright 2002 John Levon <levon@movementarian.org>
   6 *
   7 * Persistent cookie-path mappings. These are used by
   8 * profilers to convert a per-task EIP value into something
   9 * non-transitory that can be processed at a later date.
  10 * This is done by locking the dentry/vfsmnt pair in the
  11 * kernel until released by the tasks needing the persistent
  12 * objects. The tag is simply an unsigned long that refers
  13 * to the pair and can be looked up from userspace.
  14 */
  15
  16#include <linux/syscalls.h>
  17#include <linux/export.h>
  18#include <linux/slab.h>
  19#include <linux/list.h>
  20#include <linux/mount.h>
  21#include <linux/capability.h>
  22#include <linux/dcache.h>
  23#include <linux/mm.h>
  24#include <linux/err.h>
  25#include <linux/errno.h>
  26#include <linux/dcookies.h>
  27#include <linux/mutex.h>
  28#include <linux/path.h>
  29#include <linux/compat.h>
  30#include <linux/uaccess.h>
  31
  32/* The dcookies are allocated from a kmem_cache and
  33 * hashed onto a small number of lists. None of the
  34 * code here is particularly performance critical
  35 */
  36struct dcookie_struct {
  37        struct path path;
  38        struct list_head hash_list;
  39};
  40
  41static LIST_HEAD(dcookie_users);
  42static DEFINE_MUTEX(dcookie_mutex);
  43static struct kmem_cache *dcookie_cache __read_mostly;
  44static struct list_head *dcookie_hashtable __read_mostly;
  45static size_t hash_size __read_mostly;
  46
  47static inline int is_live(void)
  48{
  49        return !(list_empty(&dcookie_users));
  50}
  51
  52
  53/* The dentry is locked, its address will do for the cookie */
  54static inline unsigned long dcookie_value(struct dcookie_struct * dcs)
  55{
  56        return (unsigned long)dcs->path.dentry;
  57}
  58
  59
  60static size_t dcookie_hash(unsigned long dcookie)
  61{
  62        return (dcookie >> L1_CACHE_SHIFT) & (hash_size - 1);
  63}
  64
  65
  66static struct dcookie_struct * find_dcookie(unsigned long dcookie)
  67{
  68        struct dcookie_struct *found = NULL;
  69        struct dcookie_struct * dcs;
  70        struct list_head * pos;
  71        struct list_head * list;
  72
  73        list = dcookie_hashtable + dcookie_hash(dcookie);
  74
  75        list_for_each(pos, list) {
  76                dcs = list_entry(pos, struct dcookie_struct, hash_list);
  77                if (dcookie_value(dcs) == dcookie) {
  78                        found = dcs;
  79                        break;
  80                }
  81        }
  82
  83        return found;
  84}
  85
  86
  87static void hash_dcookie(struct dcookie_struct * dcs)
  88{
  89        struct list_head * list = dcookie_hashtable + dcookie_hash(dcookie_value(dcs));
  90        list_add(&dcs->hash_list, list);
  91}
  92
  93
  94static struct dcookie_struct *alloc_dcookie(const struct path *path)
  95{
  96        struct dcookie_struct *dcs = kmem_cache_alloc(dcookie_cache,
  97                                                        GFP_KERNEL);
  98        struct dentry *d;
  99        if (!dcs)
 100                return NULL;
 101
 102        d = path->dentry;
 103        spin_lock(&d->d_lock);
 104        d->d_flags |= DCACHE_COOKIE;
 105        spin_unlock(&d->d_lock);
 106
 107        dcs->path = *path;
 108        path_get(path);
 109        hash_dcookie(dcs);
 110        return dcs;
 111}
 112
 113
 114/* This is the main kernel-side routine that retrieves the cookie
 115 * value for a dentry/vfsmnt pair.
 116 */
 117int get_dcookie(const struct path *path, unsigned long *cookie)
 118{
 119        int err = 0;
 120        struct dcookie_struct * dcs;
 121
 122        mutex_lock(&dcookie_mutex);
 123
 124        if (!is_live()) {
 125                err = -EINVAL;
 126                goto out;
 127        }
 128
 129        if (path->dentry->d_flags & DCACHE_COOKIE) {
 130                dcs = find_dcookie((unsigned long)path->dentry);
 131        } else {
 132                dcs = alloc_dcookie(path);
 133                if (!dcs) {
 134                        err = -ENOMEM;
 135                        goto out;
 136                }
 137        }
 138
 139        *cookie = dcookie_value(dcs);
 140
 141out:
 142        mutex_unlock(&dcookie_mutex);
 143        return err;
 144}
 145
 146
 147/* And here is where the userspace process can look up the cookie value
 148 * to retrieve the path.
 149 */
 150static int do_lookup_dcookie(u64 cookie64, char __user *buf, size_t len)
 151{
 152        unsigned long cookie = (unsigned long)cookie64;
 153        int err = -EINVAL;
 154        char * kbuf;
 155        char * path;
 156        size_t pathlen;
 157        struct dcookie_struct * dcs;
 158
 159        /* we could leak path information to users
 160         * without dir read permission without this
 161         */
 162        if (!capable(CAP_SYS_ADMIN))
 163                return -EPERM;
 164
 165        mutex_lock(&dcookie_mutex);
 166
 167        if (!is_live()) {
 168                err = -EINVAL;
 169                goto out;
 170        }
 171
 172        if (!(dcs = find_dcookie(cookie)))
 173                goto out;
 174
 175        err = -ENOMEM;
 176        kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 177        if (!kbuf)
 178                goto out;
 179
 180        /* FIXME: (deleted) ? */
 181        path = d_path(&dcs->path, kbuf, PAGE_SIZE);
 182
 183        mutex_unlock(&dcookie_mutex);
 184
 185        if (IS_ERR(path)) {
 186                err = PTR_ERR(path);
 187                goto out_free;
 188        }
 189
 190        err = -ERANGE;
 191 
 192        pathlen = kbuf + PAGE_SIZE - path;
 193        if (pathlen <= len) {
 194                err = pathlen;
 195                if (copy_to_user(buf, path, pathlen))
 196                        err = -EFAULT;
 197        }
 198
 199out_free:
 200        kfree(kbuf);
 201        return err;
 202out:
 203        mutex_unlock(&dcookie_mutex);
 204        return err;
 205}
 206
 207SYSCALL_DEFINE3(lookup_dcookie, u64, cookie64, char __user *, buf, size_t, len)
 208{
 209        return do_lookup_dcookie(cookie64, buf, len);
 210}
 211
 212#ifdef CONFIG_COMPAT
 213COMPAT_SYSCALL_DEFINE4(lookup_dcookie, u32, w0, u32, w1, char __user *, buf, compat_size_t, len)
 214{
 215#ifdef __BIG_ENDIAN
 216        return do_lookup_dcookie(((u64)w0 << 32) | w1, buf, len);
 217#else
 218        return do_lookup_dcookie(((u64)w1 << 32) | w0, buf, len);
 219#endif
 220}
 221#endif
 222
 223static int dcookie_init(void)
 224{
 225        struct list_head * d;
 226        unsigned int i, hash_bits;
 227        int err = -ENOMEM;
 228
 229        dcookie_cache = kmem_cache_create("dcookie_cache",
 230                sizeof(struct dcookie_struct),
 231                0, 0, NULL);
 232
 233        if (!dcookie_cache)
 234                goto out;
 235
 236        dcookie_hashtable = kmalloc(PAGE_SIZE, GFP_KERNEL);
 237        if (!dcookie_hashtable)
 238                goto out_kmem;
 239
 240        err = 0;
 241
 242        /*
 243         * Find the power-of-two list-heads that can fit into the allocation..
 244         * We don't guarantee that "sizeof(struct list_head)" is necessarily
 245         * a power-of-two.
 246         */
 247        hash_size = PAGE_SIZE / sizeof(struct list_head);
 248        hash_bits = 0;
 249        do {
 250                hash_bits++;
 251        } while ((hash_size >> hash_bits) != 0);
 252        hash_bits--;
 253
 254        /*
 255         * Re-calculate the actual number of entries and the mask
 256         * from the number of bits we can fit.
 257         */
 258        hash_size = 1UL << hash_bits;
 259
 260        /* And initialize the newly allocated array */
 261        d = dcookie_hashtable;
 262        i = hash_size;
 263        do {
 264                INIT_LIST_HEAD(d);
 265                d++;
 266                i--;
 267        } while (i);
 268
 269out:
 270        return err;
 271out_kmem:
 272        kmem_cache_destroy(dcookie_cache);
 273        goto out;
 274}
 275
 276
 277static void free_dcookie(struct dcookie_struct * dcs)
 278{
 279        struct dentry *d = dcs->path.dentry;
 280
 281        spin_lock(&d->d_lock);
 282        d->d_flags &= ~DCACHE_COOKIE;
 283        spin_unlock(&d->d_lock);
 284
 285        path_put(&dcs->path);
 286        kmem_cache_free(dcookie_cache, dcs);
 287}
 288
 289
 290static void dcookie_exit(void)
 291{
 292        struct list_head * list;
 293        struct list_head * pos;
 294        struct list_head * pos2;
 295        struct dcookie_struct * dcs;
 296        size_t i;
 297
 298        for (i = 0; i < hash_size; ++i) {
 299                list = dcookie_hashtable + i;
 300                list_for_each_safe(pos, pos2, list) {
 301                        dcs = list_entry(pos, struct dcookie_struct, hash_list);
 302                        list_del(&dcs->hash_list);
 303                        free_dcookie(dcs);
 304                }
 305        }
 306
 307        kfree(dcookie_hashtable);
 308        kmem_cache_destroy(dcookie_cache);
 309}
 310
 311
 312struct dcookie_user {
 313        struct list_head next;
 314};
 315 
 316struct dcookie_user * dcookie_register(void)
 317{
 318        struct dcookie_user * user;
 319
 320        mutex_lock(&dcookie_mutex);
 321
 322        user = kmalloc(sizeof(struct dcookie_user), GFP_KERNEL);
 323        if (!user)
 324                goto out;
 325
 326        if (!is_live() && dcookie_init())
 327                goto out_free;
 328
 329        list_add(&user->next, &dcookie_users);
 330
 331out:
 332        mutex_unlock(&dcookie_mutex);
 333        return user;
 334out_free:
 335        kfree(user);
 336        user = NULL;
 337        goto out;
 338}
 339
 340
 341void dcookie_unregister(struct dcookie_user * user)
 342{
 343        mutex_lock(&dcookie_mutex);
 344
 345        list_del(&user->next);
 346        kfree(user);
 347
 348        if (!is_live())
 349                dcookie_exit();
 350
 351        mutex_unlock(&dcookie_mutex);
 352}
 353
 354EXPORT_SYMBOL_GPL(dcookie_register);
 355EXPORT_SYMBOL_GPL(dcookie_unregister);
 356EXPORT_SYMBOL_GPL(get_dcookie);
 357