| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * AppArmor security module |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 5 | 6 | * |
|---|
| 6 | 7 | * Copyright (C) 1998-2008 Novell/SUSE |
|---|
| 7 | 8 | * Copyright 2009-2010 Canonical Ltd. |
|---|
| 8 | | - * |
|---|
| 9 | | - * This program is free software; you can redistribute it and/or |
|---|
| 10 | | - * modify it under the terms of the GNU General Public License as |
|---|
| 11 | | - * published by the Free Software Foundation, version 2 of the |
|---|
| 12 | | - * License. |
|---|
| 13 | 9 | */ |
|---|
| 14 | 10 | |
|---|
| 15 | 11 | #include <linux/ctype.h> |
|---|
| 16 | 12 | #include <linux/security.h> |
|---|
| 17 | 13 | #include <linux/vmalloc.h> |
|---|
| 18 | | -#include <linux/module.h> |
|---|
| 14 | +#include <linux/init.h> |
|---|
| 19 | 15 | #include <linux/seq_file.h> |
|---|
| 20 | 16 | #include <linux/uaccess.h> |
|---|
| 21 | 17 | #include <linux/mount.h> |
|---|
| .. | .. |
|---|
| 23 | 19 | #include <linux/capability.h> |
|---|
| 24 | 20 | #include <linux/rcupdate.h> |
|---|
| 25 | 21 | #include <linux/fs.h> |
|---|
| 22 | +#include <linux/fs_context.h> |
|---|
| 26 | 23 | #include <linux/poll.h> |
|---|
| 24 | +#include <linux/zlib.h> |
|---|
| 27 | 25 | #include <uapi/linux/major.h> |
|---|
| 28 | 26 | #include <uapi/linux/magic.h> |
|---|
| 29 | 27 | |
|---|
| .. | .. |
|---|
| 67 | 65 | /* |
|---|
| 68 | 66 | * support fns |
|---|
| 69 | 67 | */ |
|---|
| 68 | + |
|---|
| 69 | +struct rawdata_f_data { |
|---|
| 70 | + struct aa_loaddata *loaddata; |
|---|
| 71 | +}; |
|---|
| 72 | + |
|---|
| 73 | +#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1) |
|---|
| 74 | + |
|---|
| 75 | +static void rawdata_f_data_free(struct rawdata_f_data *private) |
|---|
| 76 | +{ |
|---|
| 77 | + if (!private) |
|---|
| 78 | + return; |
|---|
| 79 | + |
|---|
| 80 | + aa_put_loaddata(private->loaddata); |
|---|
| 81 | + kvfree(private); |
|---|
| 82 | +} |
|---|
| 83 | + |
|---|
| 84 | +static struct rawdata_f_data *rawdata_f_data_alloc(size_t size) |
|---|
| 85 | +{ |
|---|
| 86 | + struct rawdata_f_data *ret; |
|---|
| 87 | + |
|---|
| 88 | + if (size > SIZE_MAX - sizeof(*ret)) |
|---|
| 89 | + return ERR_PTR(-EINVAL); |
|---|
| 90 | + |
|---|
| 91 | + ret = kvzalloc(sizeof(*ret) + size, GFP_KERNEL); |
|---|
| 92 | + if (!ret) |
|---|
| 93 | + return ERR_PTR(-ENOMEM); |
|---|
| 94 | + |
|---|
| 95 | + return ret; |
|---|
| 96 | +} |
|---|
| 70 | 97 | |
|---|
| 71 | 98 | /** |
|---|
| 72 | 99 | * aa_mangle_name - mangle a profile name to std profile layout form |
|---|
| .. | .. |
|---|
| 123 | 150 | return 0; |
|---|
| 124 | 151 | } |
|---|
| 125 | 152 | |
|---|
| 126 | | -static void aafs_i_callback(struct rcu_head *head) |
|---|
| 153 | +static void aafs_free_inode(struct inode *inode) |
|---|
| 127 | 154 | { |
|---|
| 128 | | - struct inode *inode = container_of(head, struct inode, i_rcu); |
|---|
| 129 | 155 | if (S_ISLNK(inode->i_mode)) |
|---|
| 130 | 156 | kfree(inode->i_link); |
|---|
| 131 | 157 | free_inode_nonrcu(inode); |
|---|
| 132 | 158 | } |
|---|
| 133 | 159 | |
|---|
| 134 | | -static void aafs_destroy_inode(struct inode *inode) |
|---|
| 135 | | -{ |
|---|
| 136 | | - call_rcu(&inode->i_rcu, aafs_i_callback); |
|---|
| 137 | | -} |
|---|
| 138 | | - |
|---|
| 139 | 160 | static const struct super_operations aafs_super_ops = { |
|---|
| 140 | 161 | .statfs = simple_statfs, |
|---|
| 141 | | - .destroy_inode = aafs_destroy_inode, |
|---|
| 162 | + .free_inode = aafs_free_inode, |
|---|
| 142 | 163 | .show_path = aafs_show_path, |
|---|
| 143 | 164 | }; |
|---|
| 144 | 165 | |
|---|
| 145 | | -static int fill_super(struct super_block *sb, void *data, int silent) |
|---|
| 166 | +static int apparmorfs_fill_super(struct super_block *sb, struct fs_context *fc) |
|---|
| 146 | 167 | { |
|---|
| 147 | 168 | static struct tree_descr files[] = { {""} }; |
|---|
| 148 | 169 | int error; |
|---|
| .. | .. |
|---|
| 155 | 176 | return 0; |
|---|
| 156 | 177 | } |
|---|
| 157 | 178 | |
|---|
| 158 | | -static struct dentry *aafs_mount(struct file_system_type *fs_type, |
|---|
| 159 | | - int flags, const char *dev_name, void *data) |
|---|
| 179 | +static int apparmorfs_get_tree(struct fs_context *fc) |
|---|
| 160 | 180 | { |
|---|
| 161 | | - return mount_single(fs_type, flags, data, fill_super); |
|---|
| 181 | + return get_tree_single(fc, apparmorfs_fill_super); |
|---|
| 182 | +} |
|---|
| 183 | + |
|---|
| 184 | +static const struct fs_context_operations apparmorfs_context_ops = { |
|---|
| 185 | + .get_tree = apparmorfs_get_tree, |
|---|
| 186 | +}; |
|---|
| 187 | + |
|---|
| 188 | +static int apparmorfs_init_fs_context(struct fs_context *fc) |
|---|
| 189 | +{ |
|---|
| 190 | + fc->ops = &apparmorfs_context_ops; |
|---|
| 191 | + return 0; |
|---|
| 162 | 192 | } |
|---|
| 163 | 193 | |
|---|
| 164 | 194 | static struct file_system_type aafs_ops = { |
|---|
| 165 | 195 | .owner = THIS_MODULE, |
|---|
| 166 | 196 | .name = AAFS_NAME, |
|---|
| 167 | | - .mount = aafs_mount, |
|---|
| 197 | + .init_fs_context = apparmorfs_init_fs_context, |
|---|
| 168 | 198 | .kill_sb = kill_anon_super, |
|---|
| 169 | 199 | }; |
|---|
| 170 | 200 | |
|---|
| .. | .. |
|---|
| 311 | 341 | } |
|---|
| 312 | 342 | |
|---|
| 313 | 343 | /** |
|---|
| 314 | | - * aafs_create_symlink - create a symlink in the apparmorfs filesystem |
|---|
| 315 | | - * @name: name of dentry to create |
|---|
| 316 | | - * @parent: parent directory for this dentry |
|---|
| 317 | | - * @target: if symlink, symlink target string |
|---|
| 318 | | - * @private: private data |
|---|
| 319 | | - * @iops: struct of inode_operations that should be used |
|---|
| 320 | | - * |
|---|
| 321 | | - * If @target parameter is %NULL, then the @iops parameter needs to be |
|---|
| 322 | | - * setup to handle .readlink and .get_link inode_operations. |
|---|
| 323 | | - */ |
|---|
| 324 | | -static struct dentry *aafs_create_symlink(const char *name, |
|---|
| 325 | | - struct dentry *parent, |
|---|
| 326 | | - const char *target, |
|---|
| 327 | | - void *private, |
|---|
| 328 | | - const struct inode_operations *iops) |
|---|
| 329 | | -{ |
|---|
| 330 | | - struct dentry *dent; |
|---|
| 331 | | - char *link = NULL; |
|---|
| 332 | | - |
|---|
| 333 | | - if (target) { |
|---|
| 334 | | - if (!link) |
|---|
| 335 | | - return ERR_PTR(-ENOMEM); |
|---|
| 336 | | - } |
|---|
| 337 | | - dent = aafs_create(name, S_IFLNK | 0444, parent, private, link, NULL, |
|---|
| 338 | | - iops); |
|---|
| 339 | | - if (IS_ERR(dent)) |
|---|
| 340 | | - kfree(link); |
|---|
| 341 | | - |
|---|
| 342 | | - return dent; |
|---|
| 343 | | -} |
|---|
| 344 | | - |
|---|
| 345 | | -/** |
|---|
| 346 | 344 | * aafs_remove - removes a file or directory from the apparmorfs filesystem |
|---|
| 347 | 345 | * |
|---|
| 348 | 346 | * @dentry: dentry of the file/directory/symlink to removed. |
|---|
| .. | .. |
|---|
| 403 | 401 | |
|---|
| 404 | 402 | data->size = copy_size; |
|---|
| 405 | 403 | if (copy_from_user(data->data, userbuf, copy_size)) { |
|---|
| 406 | | - kvfree(data); |
|---|
| 404 | + aa_put_loaddata(data); |
|---|
| 407 | 405 | return ERR_PTR(-EFAULT); |
|---|
| 408 | 406 | } |
|---|
| 409 | 407 | |
|---|
| .. | .. |
|---|
| 594 | 592 | |
|---|
| 595 | 593 | void __aa_bump_ns_revision(struct aa_ns *ns) |
|---|
| 596 | 594 | { |
|---|
| 597 | | - WRITE_ONCE(ns->revision, ns->revision + 1); |
|---|
| 595 | + WRITE_ONCE(ns->revision, READ_ONCE(ns->revision) + 1); |
|---|
| 598 | 596 | wake_up_interruptible(&ns->wait); |
|---|
| 599 | 597 | } |
|---|
| 600 | 598 | |
|---|
| .. | .. |
|---|
| 810 | 808 | struct multi_transaction { |
|---|
| 811 | 809 | struct kref count; |
|---|
| 812 | 810 | ssize_t size; |
|---|
| 813 | | - char data[0]; |
|---|
| 811 | + char data[]; |
|---|
| 814 | 812 | }; |
|---|
| 815 | 813 | |
|---|
| 816 | 814 | #define MULTI_TRANSACTION_LIMIT (PAGE_SIZE - sizeof(struct multi_transaction)) |
|---|
| .. | .. |
|---|
| 869 | 867 | if (!t) |
|---|
| 870 | 868 | return ERR_PTR(-ENOMEM); |
|---|
| 871 | 869 | kref_init(&t->count); |
|---|
| 872 | | - if (copy_from_user(t->data, buf, size)) |
|---|
| 870 | + if (copy_from_user(t->data, buf, size)) { |
|---|
| 871 | + put_multi_transaction(t); |
|---|
| 873 | 872 | return ERR_PTR(-EFAULT); |
|---|
| 873 | + } |
|---|
| 874 | 874 | |
|---|
| 875 | 875 | return t; |
|---|
| 876 | 876 | } |
|---|
| .. | .. |
|---|
| 1281 | 1281 | return 0; |
|---|
| 1282 | 1282 | } |
|---|
| 1283 | 1283 | |
|---|
| 1284 | +static int seq_rawdata_compressed_size_show(struct seq_file *seq, void *v) |
|---|
| 1285 | +{ |
|---|
| 1286 | + struct aa_loaddata *data = seq->private; |
|---|
| 1287 | + |
|---|
| 1288 | + seq_printf(seq, "%zu\n", data->compressed_size); |
|---|
| 1289 | + |
|---|
| 1290 | + return 0; |
|---|
| 1291 | +} |
|---|
| 1292 | + |
|---|
| 1284 | 1293 | SEQ_RAWDATA_FOPS(abi); |
|---|
| 1285 | 1294 | SEQ_RAWDATA_FOPS(revision); |
|---|
| 1286 | 1295 | SEQ_RAWDATA_FOPS(hash); |
|---|
| 1296 | +SEQ_RAWDATA_FOPS(compressed_size); |
|---|
| 1297 | + |
|---|
| 1298 | +static int deflate_decompress(char *src, size_t slen, char *dst, size_t dlen) |
|---|
| 1299 | +{ |
|---|
| 1300 | + int error; |
|---|
| 1301 | + struct z_stream_s strm; |
|---|
| 1302 | + |
|---|
| 1303 | + if (aa_g_rawdata_compression_level == 0) { |
|---|
| 1304 | + if (dlen < slen) |
|---|
| 1305 | + return -EINVAL; |
|---|
| 1306 | + memcpy(dst, src, slen); |
|---|
| 1307 | + return 0; |
|---|
| 1308 | + } |
|---|
| 1309 | + |
|---|
| 1310 | + memset(&strm, 0, sizeof(strm)); |
|---|
| 1311 | + |
|---|
| 1312 | + strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL); |
|---|
| 1313 | + if (!strm.workspace) |
|---|
| 1314 | + return -ENOMEM; |
|---|
| 1315 | + |
|---|
| 1316 | + strm.next_in = src; |
|---|
| 1317 | + strm.avail_in = slen; |
|---|
| 1318 | + |
|---|
| 1319 | + error = zlib_inflateInit(&strm); |
|---|
| 1320 | + if (error != Z_OK) { |
|---|
| 1321 | + error = -ENOMEM; |
|---|
| 1322 | + goto fail_inflate_init; |
|---|
| 1323 | + } |
|---|
| 1324 | + |
|---|
| 1325 | + strm.next_out = dst; |
|---|
| 1326 | + strm.avail_out = dlen; |
|---|
| 1327 | + |
|---|
| 1328 | + error = zlib_inflate(&strm, Z_FINISH); |
|---|
| 1329 | + if (error != Z_STREAM_END) |
|---|
| 1330 | + error = -EINVAL; |
|---|
| 1331 | + else |
|---|
| 1332 | + error = 0; |
|---|
| 1333 | + |
|---|
| 1334 | + zlib_inflateEnd(&strm); |
|---|
| 1335 | +fail_inflate_init: |
|---|
| 1336 | + kvfree(strm.workspace); |
|---|
| 1337 | + return error; |
|---|
| 1338 | +} |
|---|
| 1287 | 1339 | |
|---|
| 1288 | 1340 | static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, |
|---|
| 1289 | 1341 | loff_t *ppos) |
|---|
| 1290 | 1342 | { |
|---|
| 1291 | | - struct aa_loaddata *rawdata = file->private_data; |
|---|
| 1343 | + struct rawdata_f_data *private = file->private_data; |
|---|
| 1292 | 1344 | |
|---|
| 1293 | | - return simple_read_from_buffer(buf, size, ppos, rawdata->data, |
|---|
| 1294 | | - rawdata->size); |
|---|
| 1345 | + return simple_read_from_buffer(buf, size, ppos, |
|---|
| 1346 | + RAWDATA_F_DATA_BUF(private), |
|---|
| 1347 | + private->loaddata->size); |
|---|
| 1295 | 1348 | } |
|---|
| 1296 | 1349 | |
|---|
| 1297 | 1350 | static int rawdata_release(struct inode *inode, struct file *file) |
|---|
| 1298 | 1351 | { |
|---|
| 1299 | | - aa_put_loaddata(file->private_data); |
|---|
| 1352 | + rawdata_f_data_free(file->private_data); |
|---|
| 1300 | 1353 | |
|---|
| 1301 | 1354 | return 0; |
|---|
| 1302 | 1355 | } |
|---|
| 1303 | 1356 | |
|---|
| 1304 | 1357 | static int rawdata_open(struct inode *inode, struct file *file) |
|---|
| 1305 | 1358 | { |
|---|
| 1359 | + int error; |
|---|
| 1360 | + struct aa_loaddata *loaddata; |
|---|
| 1361 | + struct rawdata_f_data *private; |
|---|
| 1362 | + |
|---|
| 1306 | 1363 | if (!policy_view_capable(NULL)) |
|---|
| 1307 | 1364 | return -EACCES; |
|---|
| 1308 | | - file->private_data = __aa_get_loaddata(inode->i_private); |
|---|
| 1309 | | - if (!file->private_data) |
|---|
| 1365 | + |
|---|
| 1366 | + loaddata = __aa_get_loaddata(inode->i_private); |
|---|
| 1367 | + if (!loaddata) |
|---|
| 1310 | 1368 | /* lost race: this entry is being reaped */ |
|---|
| 1311 | 1369 | return -ENOENT; |
|---|
| 1312 | 1370 | |
|---|
| 1371 | + private = rawdata_f_data_alloc(loaddata->size); |
|---|
| 1372 | + if (IS_ERR(private)) { |
|---|
| 1373 | + error = PTR_ERR(private); |
|---|
| 1374 | + goto fail_private_alloc; |
|---|
| 1375 | + } |
|---|
| 1376 | + |
|---|
| 1377 | + private->loaddata = loaddata; |
|---|
| 1378 | + |
|---|
| 1379 | + error = deflate_decompress(loaddata->data, loaddata->compressed_size, |
|---|
| 1380 | + RAWDATA_F_DATA_BUF(private), |
|---|
| 1381 | + loaddata->size); |
|---|
| 1382 | + if (error) |
|---|
| 1383 | + goto fail_decompress; |
|---|
| 1384 | + |
|---|
| 1385 | + file->private_data = private; |
|---|
| 1313 | 1386 | return 0; |
|---|
| 1387 | + |
|---|
| 1388 | +fail_decompress: |
|---|
| 1389 | + rawdata_f_data_free(private); |
|---|
| 1390 | + return error; |
|---|
| 1391 | + |
|---|
| 1392 | +fail_private_alloc: |
|---|
| 1393 | + aa_put_loaddata(loaddata); |
|---|
| 1394 | + return error; |
|---|
| 1314 | 1395 | } |
|---|
| 1315 | 1396 | |
|---|
| 1316 | 1397 | static const struct file_operations rawdata_fops = { |
|---|
| .. | .. |
|---|
| 1388 | 1469 | goto fail; |
|---|
| 1389 | 1470 | rawdata->dents[AAFS_LOADDATA_HASH] = dent; |
|---|
| 1390 | 1471 | } |
|---|
| 1472 | + |
|---|
| 1473 | + dent = aafs_create_file("compressed_size", S_IFREG | 0444, dir, |
|---|
| 1474 | + rawdata, |
|---|
| 1475 | + &seq_rawdata_compressed_size_fops); |
|---|
| 1476 | + if (IS_ERR(dent)) |
|---|
| 1477 | + goto fail; |
|---|
| 1478 | + rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent; |
|---|
| 1391 | 1479 | |
|---|
| 1392 | 1480 | dent = aafs_create_file("raw_data", S_IFREG | 0444, |
|---|
| 1393 | 1481 | dir, rawdata, &rawdata_fops); |
|---|
| .. | .. |
|---|
| 1645 | 1733 | } |
|---|
| 1646 | 1734 | |
|---|
| 1647 | 1735 | if (profile->rawdata) { |
|---|
| 1648 | | - dent = aafs_create_symlink("raw_sha1", dir, NULL, |
|---|
| 1649 | | - profile->label.proxy, |
|---|
| 1650 | | - &rawdata_link_sha1_iops); |
|---|
| 1736 | + dent = aafs_create("raw_sha1", S_IFLNK | 0444, dir, |
|---|
| 1737 | + profile->label.proxy, NULL, NULL, |
|---|
| 1738 | + &rawdata_link_sha1_iops); |
|---|
| 1651 | 1739 | if (IS_ERR(dent)) |
|---|
| 1652 | 1740 | goto fail; |
|---|
| 1653 | 1741 | aa_get_proxy(profile->label.proxy); |
|---|
| 1654 | 1742 | profile->dents[AAFS_PROF_RAW_HASH] = dent; |
|---|
| 1655 | 1743 | |
|---|
| 1656 | | - dent = aafs_create_symlink("raw_abi", dir, NULL, |
|---|
| 1657 | | - profile->label.proxy, |
|---|
| 1658 | | - &rawdata_link_abi_iops); |
|---|
| 1744 | + dent = aafs_create("raw_abi", S_IFLNK | 0444, dir, |
|---|
| 1745 | + profile->label.proxy, NULL, NULL, |
|---|
| 1746 | + &rawdata_link_abi_iops); |
|---|
| 1659 | 1747 | if (IS_ERR(dent)) |
|---|
| 1660 | 1748 | goto fail; |
|---|
| 1661 | 1749 | aa_get_proxy(profile->label.proxy); |
|---|
| 1662 | 1750 | profile->dents[AAFS_PROF_RAW_ABI] = dent; |
|---|
| 1663 | 1751 | |
|---|
| 1664 | | - dent = aafs_create_symlink("raw_data", dir, NULL, |
|---|
| 1665 | | - profile->label.proxy, |
|---|
| 1666 | | - &rawdata_link_data_iops); |
|---|
| 1752 | + dent = aafs_create("raw_data", S_IFLNK | 0444, dir, |
|---|
| 1753 | + profile->label.proxy, NULL, NULL, |
|---|
| 1754 | + &rawdata_link_data_iops); |
|---|
| 1667 | 1755 | if (IS_ERR(dent)) |
|---|
| 1668 | 1756 | goto fail; |
|---|
| 1669 | 1757 | aa_get_proxy(profile->label.proxy); |
|---|
| .. | .. |
|---|
| 1749 | 1837 | if (error) |
|---|
| 1750 | 1838 | return error; |
|---|
| 1751 | 1839 | |
|---|
| 1752 | | - parent = aa_get_ns(dir->i_private); |
|---|
| 1840 | + parent = aa_get_ns(dir->i_private); |
|---|
| 1753 | 1841 | /* rmdir calls the generic securityfs functions to remove files |
|---|
| 1754 | 1842 | * from the apparmor dir. It is up to the apparmor ns locking |
|---|
| 1755 | 1843 | * to avoid races. |
|---|
| .. | .. |
|---|
| 1959 | 2047 | |
|---|
| 1960 | 2048 | return error; |
|---|
| 1961 | 2049 | } |
|---|
| 2050 | + |
|---|
| 2051 | + |
|---|
| 2052 | +#define list_entry_is_head(pos, head, member) (&pos->member == (head)) |
|---|
| 1962 | 2053 | |
|---|
| 1963 | 2054 | /** |
|---|
| 1964 | 2055 | * __next_ns - find the next namespace to list |
|---|
| .. | .. |
|---|
| 2243 | 2334 | static struct aa_sfs_entry aa_sfs_entry_policy[] = { |
|---|
| 2244 | 2335 | AA_SFS_DIR("versions", aa_sfs_entry_versions), |
|---|
| 2245 | 2336 | AA_SFS_FILE_BOOLEAN("set_load", 1), |
|---|
| 2337 | + /* number of out of band transitions supported */ |
|---|
| 2338 | + AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED), |
|---|
| 2246 | 2339 | { } |
|---|
| 2247 | 2340 | }; |
|---|
| 2248 | 2341 | |
|---|
| .. | .. |
|---|
| 2453 | 2546 | { |
|---|
| 2454 | 2547 | struct aa_ns *ns; |
|---|
| 2455 | 2548 | struct path path; |
|---|
| 2549 | + int error; |
|---|
| 2456 | 2550 | |
|---|
| 2457 | 2551 | if (!dentry) |
|---|
| 2458 | 2552 | return ERR_PTR(-ECHILD); |
|---|
| 2553 | + |
|---|
| 2459 | 2554 | ns = aa_get_current_ns(); |
|---|
| 2460 | 2555 | path.mnt = mntget(aafs_mnt); |
|---|
| 2461 | 2556 | path.dentry = dget(ns_dir(ns)); |
|---|
| 2462 | | - nd_jump_link(&path); |
|---|
| 2557 | + error = nd_jump_link(&path); |
|---|
| 2463 | 2558 | aa_put_ns(ns); |
|---|
| 2464 | 2559 | |
|---|
| 2465 | | - return NULL; |
|---|
| 2560 | + return ERR_PTR(error); |
|---|
| 2466 | 2561 | } |
|---|
| 2467 | 2562 | |
|---|
| 2468 | 2563 | static int policy_readlink(struct dentry *dentry, char __user *buffer, |
|---|