Pull namespace updates from Eric Biederman: "This is a bunch of small changes built against 3.16-rc6. The most significant change for users is the first patch which makes setns drmatically faster by removing unneded rcu handling. The next chunk of changes are so that "mount -o remount,.." will not allow the user namespace root to drop flags on a mount set by the system wide root. Aks this forces read-only mounts to stay read-only, no-dev mounts to stay no-dev, no-suid mounts to stay no-suid, no-exec mounts to stay no exec and it prevents unprivileged users from messing with a mounts atime settings. I have included my test case as the last patch in this series so people performing backports can verify this change works correctly. The next change fixes a bug in NFS that was discovered while auditing nsproxy users for the first optimization. Today you can oops the kernel by reading /proc/fs/nfsfs/{servers,volumes} if you are clever with pid namespaces. I rebased and fixed the build of the !CONFIG_NFS_FS case yesterday when a build bot caught my typo. Given that no one to my knowledge bases anything on my tree fixing the typo in place seems more responsible that requiring a typo-fix to be backported as well. The last change is a small semantic cleanup introducing /proc/thread-self and pointing /proc/mounts and /proc/net at it. This prevents several kinds of problemantic corner cases. It is a user-visible change so it has a minute chance of causing regressions so the change to /proc/mounts and /proc/net are individual one line commits that can be trivially reverted. Unfortunately I lost and could not find the email of the original reporter so he is not credited. From at least one perspective this change to /proc/net is a refgression fix to allow pthread /proc/net uses that were broken by the introduction of the network namespace" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace: proc: Point /proc/mounts at /proc/thread-self/mounts instead of /proc/self/mounts proc: Point /proc/net at /proc/thread-self/net instead of /proc/self/net proc: Implement /proc/thread-self to point at the directory of the current thread proc: Have net show up under /proc/<tgid>/task/<tid> NFS: Fix /proc/fs/nfsfs/servers and /proc/fs/nfsfs/volumes mnt: Add tests for unprivileged remount cases that have found to be faulty mnt: Change the default remount atime from relatime to the existing value mnt: Correct permission checks in do_remount mnt: Move the test for MNT_LOCK_READONLY from change_mount_flags into do_remount mnt: Only change user settable mount flags in remount namespaces: Use task_lock and not rcu to protect nsproxy
Unified · Split
| @@ -890,8 +890,21 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, · 889 unmodified lines | ||
| 890 | 890 | |
| 891 | 891 | mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~(MNT_WRITE_HOLD|MNT_MARKED); |
| 892 | 892 | /* Don't allow unprivileged users to change mount flags */ |
| 893 | − if ((flag & CL_UNPRIVILEGED) && (mnt->mnt.mnt_flags & MNT_READONLY)) | |
| 894 | − mnt->mnt.mnt_flags |= MNT_LOCK_READONLY; | |
| 893 | + if (flag & CL_UNPRIVILEGED) { | |
| 894 | + mnt->mnt.mnt_flags |= MNT_LOCK_ATIME; | |
| 895 | + | |
| 896 | + if (mnt->mnt.mnt_flags & MNT_READONLY) | |
| 897 | + mnt->mnt.mnt_flags |= MNT_LOCK_READONLY; | |
| 898 | + | |
| 899 | + if (mnt->mnt.mnt_flags & MNT_NODEV) | |
| 900 | + mnt->mnt.mnt_flags |= MNT_LOCK_NODEV; | |
| 901 | + | |
| 902 | + if (mnt->mnt.mnt_flags & MNT_NOSUID) | |
| 903 | + mnt->mnt.mnt_flags |= MNT_LOCK_NOSUID; | |
| 904 | + | |
| 905 | + if (mnt->mnt.mnt_flags & MNT_NOEXEC) | |
| 906 | + mnt->mnt.mnt_flags |= MNT_LOCK_NOEXEC; | |
| 907 | + } | |
| 895 | 908 | |
| 896 | 909 | /* Don't allow unprivileged users to reveal what is under a mount */ |
| 897 | 910 | if ((flag & CL_UNPRIVILEGED) && list_empty(&old->mnt_expire)) |
| @@ -1896,9 +1909,6 @@ static int change_mount_flags(struct vfsmount *mnt, int ms_flags) · 998 unmodified lines | ||
| 1896 | 1909 | if (readonly_request == __mnt_is_readonly(mnt)) |
| 1897 | 1910 | return 0; |
| 1898 | 1911 | |
| 1899 | − if (mnt->mnt_flags & MNT_LOCK_READONLY) | |
| 1900 | − return -EPERM; | |
| 1901 | − | |
| 1902 | 1912 | if (readonly_request) |
| 1903 | 1913 | error = mnt_make_readonly(real_mount(mnt)); |
| 1904 | 1914 | else |
| @@ -1924,6 +1934,33 @@ static int do_remount(struct path *path, int flags, int mnt_flags, · 19 unmodified lines | ||
| 1924 | 1934 | if (path->dentry != path->mnt->mnt_root) |
| 1925 | 1935 | return -EINVAL; |
| 1926 | 1936 | |
| 1937 | + /* Don't allow changing of locked mnt flags. | |
| 1938 | + * | |
| 1939 | + * No locks need to be held here while testing the various | |
| 1940 | + * MNT_LOCK flags because those flags can never be cleared | |
| 1941 | + * once they are set. | |
| 1942 | + */ | |
| 1943 | + if ((mnt->mnt.mnt_flags & MNT_LOCK_READONLY) && | |
| 1944 | + !(mnt_flags & MNT_READONLY)) { | |
| 1945 | + return -EPERM; | |
| 1946 | + } | |
| 1947 | + if ((mnt->mnt.mnt_flags & MNT_LOCK_NODEV) && | |
| 1948 | + !(mnt_flags & MNT_NODEV)) { | |
| 1949 | + return -EPERM; | |
| 1950 | + } | |
| 1951 | + if ((mnt->mnt.mnt_flags & MNT_LOCK_NOSUID) && | |
| 1952 | + !(mnt_flags & MNT_NOSUID)) { | |
| 1953 | + return -EPERM; | |
| 1954 | + } | |
| 1955 | + if ((mnt->mnt.mnt_flags & MNT_LOCK_NOEXEC) && | |
| 1956 | + !(mnt_flags & MNT_NOEXEC)) { | |
| 1957 | + return -EPERM; | |
| 1958 | + } | |
| 1959 | + if ((mnt->mnt.mnt_flags & MNT_LOCK_ATIME) && | |
| 1960 | + ((mnt->mnt.mnt_flags & MNT_ATIME_MASK) != (mnt_flags & MNT_ATIME_MASK))) { | |
| 1961 | + return -EPERM; | |
| 1962 | + } | |
| 1963 | + | |
| 1927 | 1964 | err = security_sb_remount(sb, data); |
| 1928 | 1965 | if (err) |
| 1929 | 1966 | return err; |
| @@ -1937,7 +1974,7 @@ static int do_remount(struct path *path, int flags, int mnt_flags, · 7 unmodified lines | ||
| 1937 | 1974 | err = do_remount_sb(sb, flags, data, 0); |
| 1938 | 1975 | if (!err) { |
| 1939 | 1976 | lock_mount_hash(); |
| 1940 | − mnt_flags |= mnt->mnt.mnt_flags & MNT_PROPAGATION_MASK; | |
| 1977 | + mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK; | |
| 1941 | 1978 | mnt->mnt.mnt_flags = mnt_flags; |
| 1942 | 1979 | touch_mnt_namespace(mnt->mnt_ns); |
| 1943 | 1980 | unlock_mount_hash(); |
| @@ -2122,7 +2159,7 @@ static int do_new_mount(struct path *path, const char *fstype, int flags, · 178 unmodified lines | ||
| 2122 | 2159 | */ |
| 2123 | 2160 | if (!(type->fs_flags & FS_USERNS_DEV_MOUNT)) { |
| 2124 | 2161 | flags |= MS_NODEV; |
| 2125 | − mnt_flags |= MNT_NODEV; | |
| 2162 | + mnt_flags |= MNT_NODEV | MNT_LOCK_NODEV; | |
| 2126 | 2163 | } |
| 2127 | 2164 | } |
| 2128 | 2165 | |
| @@ -2436,6 +2473,14 @@ long do_mount(const char *dev_name, const char *dir_name, · 307 unmodified lines | ||
| 2436 | 2473 | if (flags & MS_RDONLY) |
| 2437 | 2474 | mnt_flags |= MNT_READONLY; |
| 2438 | 2475 | |
| 2476 | + /* The default atime for remount is preservation */ | |
| 2477 | + if ((flags & MS_REMOUNT) && | |
| 2478 | + ((flags & (MS_NOATIME | MS_NODIRATIME | MS_RELATIME | | |
| 2479 | + MS_STRICTATIME)) == 0)) { | |
| 2480 | + mnt_flags &= ~MNT_ATIME_MASK; | |
| 2481 | + mnt_flags |= path.mnt->mnt_flags & MNT_ATIME_MASK; | |
| 2482 | + } | |
| 2483 | + | |
| 2439 | 2484 | flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN | |
| 2440 | 2485 | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | |
| 2441 | 2486 | MS_STRICTATIME); |
| @@ -2972,13 +3017,13 @@ static void *mntns_get(struct task_struct *task) · 530 unmodified lines | ||
| 2972 | 3017 | struct mnt_namespace *ns = NULL; |
| 2973 | 3018 | struct nsproxy *nsproxy; |
| 2974 | 3019 | |
| 2975 | − rcu_read_lock(); | |
| 2976 | − nsproxy = task_nsproxy(task); | |
| 3020 | + task_lock(task); | |
| 3021 | + nsproxy = task->nsproxy; | |
| 2977 | 3022 | if (nsproxy) { |
| 2978 | 3023 | ns = nsproxy->mnt_ns; |
| 2979 | 3024 | get_mnt_ns(ns); |
| 2980 | 3025 | } |
| 2981 | − rcu_read_unlock(); | |
| 3026 | + task_unlock(task); | |
| 2982 | 3027 | |
| 2983 | 3028 | return ns; |
| 2984 | 3029 | } |
| @@ -1205,7 +1205,7 @@ static const struct file_operations nfs_server_list_fops = { · 1204 unmodified lines | ||
| 1205 | 1205 | .open = nfs_server_list_open, |
| 1206 | 1206 | .read = seq_read, |
| 1207 | 1207 | .llseek = seq_lseek, |
| 1208 | − .release = seq_release, | |
| 1208 | + .release = seq_release_net, | |
| 1209 | 1209 | .owner = THIS_MODULE, |
| 1210 | 1210 | }; |
| 1211 | 1211 | |
| @@ -1226,7 +1226,7 @@ static const struct file_operations nfs_volume_list_fops = { · 14 unmodified lines | ||
| 1226 | 1226 | .open = nfs_volume_list_open, |
| 1227 | 1227 | .read = seq_read, |
| 1228 | 1228 | .llseek = seq_lseek, |
| 1229 | − .release = seq_release, | |
| 1229 | + .release = seq_release_net, | |
| 1230 | 1230 | .owner = THIS_MODULE, |
| 1231 | 1231 | }; |
| 1232 | 1232 | |
| @@ -1236,19 +1236,8 @@ static const struct file_operations nfs_volume_list_fops = { · 3 unmodified lines | ||
| 1236 | 1236 | */ |
| 1237 | 1237 | static int nfs_server_list_open(struct inode *inode, struct file *file) |
| 1238 | 1238 | { |
| 1239 | − struct seq_file *m; | |
| 1240 | − int ret; | |
| 1241 | − struct pid_namespace *pid_ns = file->f_dentry->d_sb->s_fs_info; | |
| 1242 | − struct net *net = pid_ns->child_reaper->nsproxy->net_ns; | |
| 1243 | − | |
| 1244 | − ret = seq_open(file, &nfs_server_list_ops); | |
| 1245 | − if (ret < 0) | |
| 1246 | − return ret; | |
| 1247 | − | |
| 1248 | − m = file->private_data; | |
| 1249 | − m->private = net; | |
| 1250 | − | |
| 1251 | − return 0; | |
| 1239 | + return seq_open_net(inode, file, &nfs_server_list_ops, | |
| 1240 | + sizeof(struct seq_net_private)); | |
| 1252 | 1241 | } |
| 1253 | 1242 | |
| 1254 | 1243 | /* |
| @@ -1256,7 +1245,7 @@ static int nfs_server_list_open(struct inode *inode, struct file *file) · 1 unmodified line | ||
| 1256 | 1245 | */ |
| 1257 | 1246 | static void *nfs_server_list_start(struct seq_file *m, loff_t *_pos) |
| 1258 | 1247 | { |
| 1259 | − struct nfs_net *nn = net_generic(m->private, nfs_net_id); | |
| 1248 | + struct nfs_net *nn = net_generic(seq_file_net(m), nfs_net_id); | |
| 1260 | 1249 | |
| 1261 | 1250 | /* lock the list against modification */ |
| 1262 | 1251 | spin_lock(&nn->nfs_client_lock); |
| @@ -1268,7 +1257,7 @@ static void *nfs_server_list_start(struct seq_file *m, loff_t *_pos) · 5 unmodified lines | ||
| 1268 | 1257 | */ |
| 1269 | 1258 | static void *nfs_server_list_next(struct seq_file *p, void *v, loff_t *pos) |
| 1270 | 1259 | { |
| 1271 | − struct nfs_net *nn = net_generic(p->private, nfs_net_id); | |
| 1260 | + struct nfs_net *nn = net_generic(seq_file_net(p), nfs_net_id); | |
| 1272 | 1261 | |
| 1273 | 1262 | return seq_list_next(v, &nn->nfs_client_list, pos); |
| 1274 | 1263 | } |
| @@ -1278,7 +1267,7 @@ static void *nfs_server_list_next(struct seq_file *p, void *v, loff_t *pos) · 3 unmodified lines | ||
| 1278 | 1267 | */ |
| 1279 | 1268 | static void nfs_server_list_stop(struct seq_file *p, void *v) |
| 1280 | 1269 | { |
| 1281 | − struct nfs_net *nn = net_generic(p->private, nfs_net_id); | |
| 1270 | + struct nfs_net *nn = net_generic(seq_file_net(p), nfs_net_id); | |
| 1282 | 1271 | |
| 1283 | 1272 | spin_unlock(&nn->nfs_client_lock); |
| 1284 | 1273 | } |
| @@ -1289,7 +1278,7 @@ static void nfs_server_list_stop(struct seq_file *p, void *v) · 4 unmodified lines | ||
| 1289 | 1278 | static int nfs_server_list_show(struct seq_file *m, void *v) |
| 1290 | 1279 | { |
| 1291 | 1280 | struct nfs_client *clp; |
| 1292 | − struct nfs_net *nn = net_generic(m->private, nfs_net_id); | |
| 1281 | + struct nfs_net *nn = net_generic(seq_file_net(m), nfs_net_id); | |
| 1293 | 1282 | |
| 1294 | 1283 | /* display header on line 1 */ |
| 1295 | 1284 | if (v == &nn->nfs_client_list) { |
| @@ -1321,19 +1310,8 @@ static int nfs_server_list_show(struct seq_file *m, void *v) · 25 unmodified lines | ||
| 1321 | 1310 | */ |
| 1322 | 1311 | static int nfs_volume_list_open(struct inode *inode, struct file *file) |
| 1323 | 1312 | { |
| 1324 | − struct seq_file *m; | |
| 1325 | − int ret; | |
| 1326 | − struct pid_namespace *pid_ns = file->f_dentry->d_sb->s_fs_info; | |
| 1327 | − struct net *net = pid_ns->child_reaper->nsproxy->net_ns; | |
| 1328 | − | |
| 1329 | − ret = seq_open(file, &nfs_volume_list_ops); | |
| 1330 | − if (ret < 0) | |
| 1331 | − return ret; | |
| 1332 | − | |
| 1333 | − m = file->private_data; | |
| 1334 | − m->private = net; | |
| 1335 | − | |
| 1336 | − return 0; | |
| 1313 | + return seq_open_net(inode, file, &nfs_server_list_ops, | |
| 1314 | + sizeof(struct seq_net_private)); | |
| 1337 | 1315 | } |
| 1338 | 1316 | |
| 1339 | 1317 | /* |
| @@ -1341,7 +1319,7 @@ static int nfs_volume_list_open(struct inode *inode, struct file *file) · 1 unmodified line | ||
| 1341 | 1319 | */ |
| 1342 | 1320 | static void *nfs_volume_list_start(struct seq_file *m, loff_t *_pos) |
| 1343 | 1321 | { |
| 1344 | − struct nfs_net *nn = net_generic(m->private, nfs_net_id); | |
| 1322 | + struct nfs_net *nn = net_generic(seq_file_net(m), nfs_net_id); | |
| 1345 | 1323 | |
| 1346 | 1324 | /* lock the list against modification */ |
| 1347 | 1325 | spin_lock(&nn->nfs_client_lock); |
| @@ -1353,7 +1331,7 @@ static void *nfs_volume_list_start(struct seq_file *m, loff_t *_pos) · 5 unmodified lines | ||
| 1353 | 1331 | */ |
| 1354 | 1332 | static void *nfs_volume_list_next(struct seq_file *p, void *v, loff_t *pos) |
| 1355 | 1333 | { |
| 1356 | − struct nfs_net *nn = net_generic(p->private, nfs_net_id); | |
| 1334 | + struct nfs_net *nn = net_generic(seq_file_net(p), nfs_net_id); | |
| 1357 | 1335 | |
| 1358 | 1336 | return seq_list_next(v, &nn->nfs_volume_list, pos); |
| 1359 | 1337 | } |
| @@ -1363,7 +1341,7 @@ static void *nfs_volume_list_next(struct seq_file *p, void *v, loff_t *pos) · 3 unmodified lines | ||
| 1363 | 1341 | */ |
| 1364 | 1342 | static void nfs_volume_list_stop(struct seq_file *p, void *v) |
| 1365 | 1343 | { |
| 1366 | − struct nfs_net *nn = net_generic(p->private, nfs_net_id); | |
| 1344 | + struct nfs_net *nn = net_generic(seq_file_net(p), nfs_net_id); | |
| 1367 | 1345 | |
| 1368 | 1346 | spin_unlock(&nn->nfs_client_lock); |
| 1369 | 1347 | } |
| @@ -1376,7 +1354,7 @@ static int nfs_volume_list_show(struct seq_file *m, void *v) · 6 unmodified lines | ||
| 1376 | 1354 | struct nfs_server *server; |
| 1377 | 1355 | struct nfs_client *clp; |
| 1378 | 1356 | char dev[8], fsid[17]; |
| 1379 | − struct nfs_net *nn = net_generic(m->private, nfs_net_id); | |
| 1357 | + struct nfs_net *nn = net_generic(seq_file_net(m), nfs_net_id); | |
| 1380 | 1358 | |
| 1381 | 1359 | /* display header on line 1 */ |
| 1382 | 1360 | if (v == &nn->nfs_volume_list) { |
| @@ -1407,6 +1385,45 @@ static int nfs_volume_list_show(struct seq_file *m, void *v) · 24 unmodified lines | ||
| 1407 | 1385 | return 0; |
| 1408 | 1386 | } |
| 1409 | 1387 | |
| 1388 | +int nfs_fs_proc_net_init(struct net *net) | |
| 1389 | +{ | |
| 1390 | + struct nfs_net *nn = net_generic(net, nfs_net_id); | |
| 1391 | + struct proc_dir_entry *p; | |
| 1392 | + | |
| 1393 | + nn->proc_nfsfs = proc_net_mkdir(net, "nfsfs", net->proc_net); | |
| 1394 | + if (!nn->proc_nfsfs) | |
| 1395 | + goto error_0; | |
| 1396 | + | |
| 1397 | + /* a file of servers with which we're dealing */ | |
| 1398 | + p = proc_create("servers", S_IFREG|S_IRUGO, | |
| 1399 | + nn->proc_nfsfs, &nfs_server_list_fops); | |
| 1400 | + if (!p) | |
| 1401 | + goto error_1; | |
| 1402 | + | |
| 1403 | + /* a file of volumes that we have mounted */ | |
| 1404 | + p = proc_create("volumes", S_IFREG|S_IRUGO, | |
| 1405 | + nn->proc_nfsfs, &nfs_volume_list_fops); | |
| 1406 | + if (!p) | |
| 1407 | + goto error_2; | |
| 1408 | + return 0; | |
| 1409 | + | |
| 1410 | +error_2: | |
| 1411 | + remove_proc_entry("servers", nn->proc_nfsfs); | |
| 1412 | +error_1: | |
| 1413 | + remove_proc_entry("fs/nfsfs", NULL); | |
| 1414 | +error_0: | |
| 1415 | + return -ENOMEM; | |
| 1416 | +} | |
| 1417 | + | |
| 1418 | +void nfs_fs_proc_net_exit(struct net *net) | |
| 1419 | +{ | |
| 1420 | + struct nfs_net *nn = net_generic(net, nfs_net_id); | |
| 1421 | + | |
| 1422 | + remove_proc_entry("volumes", nn->proc_nfsfs); | |
| 1423 | + remove_proc_entry("servers", nn->proc_nfsfs); | |
| 1424 | + remove_proc_entry("fs/nfsfs", NULL); | |
| 1425 | +} | |
| 1426 | + | |
| 1410 | 1427 | /* |
| 1411 | 1428 | * initialise the /proc/fs/nfsfs/ directory |
| 1412 | 1429 | */ |
| @@ -1419,14 +1436,12 @@ int __init nfs_fs_proc_init(void) · 6 unmodified lines | ||
| 1419 | 1436 | goto error_0; |
| 1420 | 1437 | |
| 1421 | 1438 | /* a file of servers with which we're dealing */ |
| 1422 | − p = proc_create("servers", S_IFREG|S_IRUGO, | |
| 1423 | − proc_fs_nfs, &nfs_server_list_fops); | |
| 1439 | + p = proc_symlink("servers", proc_fs_nfs, "../../net/nfsfs/servers"); | |
| 1424 | 1440 | if (!p) |
| 1425 | 1441 | goto error_1; |
| 1426 | 1442 | |
| 1427 | 1443 | /* a file of volumes that we have mounted */ |
| 1428 | − p = proc_create("volumes", S_IFREG|S_IRUGO, | |
| 1429 | − proc_fs_nfs, &nfs_volume_list_fops); | |
| 1444 | + p = proc_symlink("volumes", proc_fs_nfs, "../../net/nfsfs/volumes"); | |
| 1430 | 1445 | if (!p) |
| 1431 | 1446 | goto error_2; |
| 1432 | 1447 | return 0; |
| @@ -1840,11 +1840,12 @@ EXPORT_SYMBOL_GPL(nfs_net_id); · 1839 unmodified lines | ||
| 1840 | 1840 | static int nfs_net_init(struct net *net) |
| 1841 | 1841 | { |
| 1842 | 1842 | nfs_clients_init(net); |
| 1843 | − return 0; | |
| 1843 | + return nfs_fs_proc_net_init(net); | |
| 1844 | 1844 | } |
| 1845 | 1845 | |
| 1846 | 1846 | static void nfs_net_exit(struct net *net) |
| 1847 | 1847 | { |
| 1848 | + nfs_fs_proc_net_exit(net); | |
| 1848 | 1849 | nfs_cleanup_cb_ident_idr(net); |
| 1849 | 1850 | } |
| 1850 | 1851 | |
| @@ -195,7 +195,16 @@ extern struct rpc_clnt *nfs4_find_or_create_ds_client(struct nfs_client *, · 194 unmodified lines | ||
| 195 | 195 | #ifdef CONFIG_PROC_FS |
| 196 | 196 | extern int __init nfs_fs_proc_init(void); |
| 197 | 197 | extern void nfs_fs_proc_exit(void); |
| 198 | +extern int nfs_fs_proc_net_init(struct net *net); | |
| 199 | +extern void nfs_fs_proc_net_exit(struct net *net); | |
| 198 | 200 | #else |
| 201 | +static inline int nfs_fs_proc_net_init(struct net *net) | |
| 202 | +{ | |
| 203 | + return 0; | |
| 204 | +} | |
| 205 | +static inline void nfs_fs_proc_net_exit(struct net *net) | |
| 206 | +{ | |
| 207 | +} | |
| 199 | 208 | static inline int nfs_fs_proc_init(void) |
| 200 | 209 | { |
| 201 | 210 | return 0; |
| @@ -29,6 +29,9 @@ struct nfs_net { · 28 unmodified lines | ||
| 29 | 29 | #endif |
| 30 | 30 | spinlock_t nfs_client_lock; |
| 31 | 31 | struct timespec boot_time; |
| 32 | +#ifdef CONFIG_PROC_FS | |
| 33 | + struct proc_dir_entry *proc_nfsfs; | |
| 34 | +#endif | |
| 32 | 35 | }; |
| 33 | 36 | |
| 34 | 37 | extern int nfs_net_id; |
| @@ -23,6 +23,7 @@ proc-y += version.o · 22 unmodified lines | ||
| 23 | 23 | proc-y += softirqs.o |
| 24 | 24 | proc-y += namespaces.o |
| 25 | 25 | proc-y += self.o |
| 26 | +proc-y += thread_self.o | |
| 26 | 27 | proc-$(CONFIG_PROC_SYSCTL) += proc_sysctl.o |
| 27 | 28 | proc-$(CONFIG_NET) += proc_net.o |
| 28 | 29 | proc-$(CONFIG_PROC_KCORE) += kcore.o |
| @@ -2814,7 +2814,7 @@ retry: · 2813 unmodified lines | ||
| 2814 | 2814 | return iter; |
| 2815 | 2815 | } |
| 2816 | 2816 | |
| 2817 | −#define TGID_OFFSET (FIRST_PROCESS_ENTRY + 1) | |
| 2817 | +#define TGID_OFFSET (FIRST_PROCESS_ENTRY + 2) | |
| 2818 | 2818 | |
| 2819 | 2819 | /* for the /proc/ directory itself, after non-process stuff has been done */ |
| 2820 | 2820 | int proc_pid_readdir(struct file *file, struct dir_context *ctx) |
| @@ -2826,14 +2826,19 @@ int proc_pid_readdir(struct file *file, struct dir_context *ctx) · 5 unmodified lines | ||
| 2826 | 2826 | if (pos >= PID_MAX_LIMIT + TGID_OFFSET) |
| 2827 | 2827 | return 0; |
| 2828 | 2828 | |
| 2829 | − if (pos == TGID_OFFSET - 1) { | |
| 2829 | + if (pos == TGID_OFFSET - 2) { | |
| 2830 | 2830 | struct inode *inode = ns->proc_self->d_inode; |
| 2831 | 2831 | if (!dir_emit(ctx, "self", 4, inode->i_ino, DT_LNK)) |
| 2832 | 2832 | return 0; |
| 2833 | − iter.tgid = 0; | |
| 2834 | − } else { | |
| 2835 | − iter.tgid = pos - TGID_OFFSET; | |
| 2833 | + ctx->pos = pos = pos + 1; | |
| 2834 | + } | |
| 2835 | + if (pos == TGID_OFFSET - 1) { | |
| 2836 | + struct inode *inode = ns->proc_thread_self->d_inode; | |
| 2837 | + if (!dir_emit(ctx, "thread-self", 11, inode->i_ino, DT_LNK)) | |
| 2838 | + return 0; | |
| 2839 | + ctx->pos = pos = pos + 1; | |
| 2836 | 2840 | } |
| 2841 | + iter.tgid = pos - TGID_OFFSET; | |
| 2837 | 2842 | iter.task = NULL; |
| 2838 | 2843 | for (iter = next_tgid(ns, iter); |
| 2839 | 2844 | iter.task; |
| @@ -2862,6 +2867,9 @@ static const struct pid_entry tid_base_stuff[] = { · 22 unmodified lines | ||
| 2862 | 2867 | DIR("fd", S_IRUSR|S_IXUSR, proc_fd_inode_operations, proc_fd_operations), |
| 2863 | 2868 | DIR("fdinfo", S_IRUSR|S_IXUSR, proc_fdinfo_inode_operations, proc_fdinfo_operations), |
| 2864 | 2869 | DIR("ns", S_IRUSR|S_IXUGO, proc_ns_dir_inode_operations, proc_ns_dir_operations), |
| 2870 | +#ifdef CONFIG_NET | |
| 2871 | + DIR("net", S_IRUGO|S_IXUGO, proc_net_inode_operations, proc_net_operations), | |
| 2872 | +#endif | |
| 2865 | 2873 | REG("environ", S_IRUSR, proc_environ_operations), |
| 2866 | 2874 | ONE("auxv", S_IRUSR, proc_pid_auxv), |
| 2867 | 2875 | ONE("status", S_IRUGO, proc_pid_status), |
| @@ -442,6 +442,7 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de) · 441 unmodified lines | ||
| 442 | 442 | int proc_fill_super(struct super_block *s) |
| 443 | 443 | { |
| 444 | 444 | struct inode *root_inode; |
| 445 | + int ret; | |
| 445 | 446 | |
| 446 | 447 | s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC; |
| 447 | 448 | s->s_blocksize = 1024; |
| @@ -463,5 +464,9 @@ int proc_fill_super(struct super_block *s) · 15 unmodified lines | ||
| 463 | 464 | return -ENOMEM; |
| 464 | 465 | } |
| 465 | 466 | |
| 466 | − return proc_setup_self(s); | |
| 467 | + ret = proc_setup_self(s); | |
| 468 | + if (ret) { | |
| 469 | + return ret; | |
| 470 | + } | |
| 471 | + return proc_setup_thread_self(s); | |
| 467 | 472 | } |
| @@ -230,6 +230,12 @@ static inline int proc_net_init(void) { return 0; } · 229 unmodified lines | ||
| 230 | 230 | */ |
| 231 | 231 | extern int proc_setup_self(struct super_block *); |
| 232 | 232 | |
| 233 | +/* | |
| 234 | + * proc_thread_self.c | |
| 235 | + */ | |
| 236 | +extern int proc_setup_thread_self(struct super_block *); | |
| 237 | +extern void proc_thread_self_init(void); | |
| 238 | + | |
| 233 | 239 | /* |
| 234 | 240 | * proc_sysctl.c |
| 235 | 241 | */ |
| @@ -113,9 +113,11 @@ static struct net *get_proc_task_net(struct inode *dir) · 112 unmodified lines | ||
| 113 | 113 | rcu_read_lock(); |
| 114 | 114 | task = pid_task(proc_pid(dir), PIDTYPE_PID); |
| 115 | 115 | if (task != NULL) { |
| 116 | − ns = task_nsproxy(task); | |
| 116 | + task_lock(task); | |
| 117 | + ns = task->nsproxy; | |
| 117 | 118 | if (ns != NULL) |
| 118 | 119 | net = get_net(ns->net_ns); |
| 120 | + task_unlock(task); | |
| 119 | 121 | } |
| 120 | 122 | rcu_read_unlock(); |
| 121 | 123 | |
| @@ -224,7 +226,7 @@ static struct pernet_operations __net_initdata proc_net_ns_ops = { · 102 unmodified lines | ||
| 224 | 226 | |
| 225 | 227 | int __init proc_net_init(void) |
| 226 | 228 | { |
| 227 | − proc_symlink("net", NULL, "self/net"); | |
| 229 | + proc_symlink("net", NULL, "thread-self/net"); | |
| 228 | 230 | |
| 229 | 231 | return register_pernet_subsys(&proc_net_ns_ops); |
| 230 | 232 | } |
| @@ -149,6 +149,8 @@ static void proc_kill_sb(struct super_block *sb) · 148 unmodified lines | ||
| 149 | 149 | ns = (struct pid_namespace *)sb->s_fs_info; |
| 150 | 150 | if (ns->proc_self) |
| 151 | 151 | dput(ns->proc_self); |
| 152 | + if (ns->proc_thread_self) | |
| 153 | + dput(ns->proc_thread_self); | |
| 152 | 154 | kill_anon_super(sb); |
| 153 | 155 | put_pid_ns(ns); |
| 154 | 156 | } |
| @@ -170,7 +172,8 @@ void __init proc_root_init(void) · 15 unmodified lines | ||
| 170 | 172 | return; |
| 171 | 173 | |
| 172 | 174 | proc_self_init(); |
| 173 | − proc_symlink("mounts", NULL, "self/mounts"); | |
| 175 | + proc_thread_self_init(); | |
| 176 | + proc_symlink("mounts", NULL, "thread-self/mounts"); | |
| 174 | 177 | |
| 175 | 178 | proc_net_init(); |
| 176 | 179 | |
| @@ -0,0 +1,85 @@ | ||
| 1 | +#include <linux/sched.h> | |
| 2 | +#include <linux/namei.h> | |
| 3 | +#include <linux/slab.h> | |
| 4 | +#include <linux/pid_namespace.h> | |
| 5 | +#include "internal.h" | |
| 6 | + | |
| 7 | +/* | |
| 8 | + * /proc/thread_self: | |
| 9 | + */ | |
| 10 | +static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer, | |
| 11 | + int buflen) | |
| 12 | +{ | |
| 13 | + struct pid_namespace *ns = dentry->d_sb->s_fs_info; | |
| 14 | + pid_t tgid = task_tgid_nr_ns(current, ns); | |
| 15 | + pid_t pid = task_pid_nr_ns(current, ns); | |
| 16 | + char tmp[PROC_NUMBUF + 6 + PROC_NUMBUF]; | |
| 17 | + if (!pid) | |
| 18 | + return -ENOENT; | |
| 19 | + sprintf(tmp, "%d/task/%d", tgid, pid); | |
| 20 | + return readlink_copy(buffer, buflen, tmp); | |
| 21 | +} | |
| 22 | + | |
| 23 | +static void *proc_thread_self_follow_link(struct dentry *dentry, struct nameidata *nd) | |
| 24 | +{ | |
| 25 | + struct pid_namespace *ns = dentry->d_sb->s_fs_info; | |
| 26 | + pid_t tgid = task_tgid_nr_ns(current, ns); | |
| 27 | + pid_t pid = task_pid_nr_ns(current, ns); | |
| 28 | + char *name = ERR_PTR(-ENOENT); | |
| 29 | + if (pid) { | |
| 30 | + name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL); | |
| 31 | + if (!name) | |
| 32 | + name = ERR_PTR(-ENOMEM); | |
| 33 | + else | |
| 34 | + sprintf(name, "%d/task/%d", tgid, pid); | |
| 35 | + } | |
| 36 | + nd_set_link(nd, name); | |
| 37 | + return NULL; | |
| 38 | +} | |
| 39 | + | |
| 40 | +static const struct inode_operations proc_thread_self_inode_operations = { | |
| 41 | + .readlink = proc_thread_self_readlink, | |
| 42 | + .follow_link = proc_thread_self_follow_link, | |
| 43 | + .put_link = kfree_put_link, | |
| 44 | +}; | |
| 45 | + | |
| 46 | +static unsigned thread_self_inum; | |
| 47 | + | |
| 48 | +int proc_setup_thread_self(struct super_block *s) | |
| 49 | +{ | |
| 50 | + struct inode *root_inode = s->s_root->d_inode; | |
| 51 | + struct pid_namespace *ns = s->s_fs_info; | |
| 52 | + struct dentry *thread_self; | |
| 53 | + | |
| 54 | + mutex_lock(&root_inode->i_mutex); | |
| 55 | + thread_self = d_alloc_name(s->s_root, "thread-self"); | |
| 56 | + if (thread_self) { | |
| 57 | + struct inode *inode = new_inode_pseudo(s); | |
| 58 | + if (inode) { | |
| 59 | + inode->i_ino = thread_self_inum; | |
| 60 | + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; | |
| 61 | + inode->i_mode = S_IFLNK | S_IRWXUGO; | |
| 62 | + inode->i_uid = GLOBAL_ROOT_UID; | |
| 63 | + inode->i_gid = GLOBAL_ROOT_GID; | |
| 64 | + inode->i_op = &proc_thread_self_inode_operations; | |
| 65 | + d_add(thread_self, inode); | |
| 66 | + } else { | |
| 67 | + dput(thread_self); | |
| 68 | + thread_self = ERR_PTR(-ENOMEM); | |
| 69 | + } | |
| 70 | + } else { | |
| 71 | + thread_self = ERR_PTR(-ENOMEM); | |
| 72 | + } | |
| 73 | + mutex_unlock(&root_inode->i_mutex); | |
| 74 | + if (IS_ERR(thread_self)) { | |
| 75 | + pr_err("proc_fill_super: can't allocate /proc/thread_self\n"); | |
| 76 | + return PTR_ERR(thread_self); | |
| 77 | + } | |
| 78 | + ns->proc_thread_self = thread_self; | |
| 79 | + return 0; | |
| 80 | +} | |
| 81 | + | |
| 82 | +void __init proc_thread_self_init(void) | |
| 83 | +{ | |
| 84 | + proc_alloc_inum(&thread_self_inum); | |
| 85 | +} | |
| @@ -232,17 +232,15 @@ static int mounts_open_common(struct inode *inode, struct file *file, · 231 unmodified lines | ||
| 232 | 232 | if (!task) |
| 233 | 233 | goto err; |
| 234 | 234 | |
| 235 | − rcu_read_lock(); | |
| 236 | − nsp = task_nsproxy(task); | |
| 235 | + task_lock(task); | |
| 236 | + nsp = task->nsproxy; | |
| 237 | 237 | if (!nsp || !nsp->mnt_ns) { |
| 238 | − rcu_read_unlock(); | |
| 238 | + task_unlock(task); | |
| 239 | 239 | put_task_struct(task); |
| 240 | 240 | goto err; |
| 241 | 241 | } |
| 242 | 242 | ns = nsp->mnt_ns; |
| 243 | 243 | get_mnt_ns(ns); |
| 244 | − rcu_read_unlock(); | |
| 245 | − task_lock(task); | |
| 246 | 244 | if (!task->fs) { |
| 247 | 245 | task_unlock(task); |
| 248 | 246 | put_task_struct(task); |
| @@ -42,13 +42,20 @@ struct mnt_namespace; · 41 unmodified lines | ||
| 42 | 42 | * flag, consider how it interacts with shared mounts. |
| 43 | 43 | */ |
| 44 | 44 | #define MNT_SHARED_MASK (MNT_UNBINDABLE) |
| 45 | −#define MNT_PROPAGATION_MASK (MNT_SHARED | MNT_UNBINDABLE) | |
| 45 | +#define MNT_USER_SETTABLE_MASK (MNT_NOSUID | MNT_NODEV | MNT_NOEXEC \ | |
| 46 | + | MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME \ | |
| 47 | + | MNT_READONLY) | |
| 48 | +#define MNT_ATIME_MASK (MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME ) | |
| 46 | 49 | |
| 47 | 50 | #define MNT_INTERNAL_FLAGS (MNT_SHARED | MNT_WRITE_HOLD | MNT_INTERNAL | \ |
| 48 | 51 | MNT_DOOMED | MNT_SYNC_UMOUNT | MNT_MARKED) |
| 49 | 52 | |
| 50 | 53 | #define MNT_INTERNAL 0x4000 |
| 51 | 54 | |
| 55 | +#define MNT_LOCK_ATIME 0x040000 | |
| 56 | +#define MNT_LOCK_NOEXEC 0x080000 | |
| 57 | +#define MNT_LOCK_NOSUID 0x100000 | |
| 58 | +#define MNT_LOCK_NODEV 0x200000 | |
| 52 | 59 | #define MNT_LOCK_READONLY 0x400000 |
| 53 | 60 | #define MNT_LOCKED 0x800000 |
| 54 | 61 | #define MNT_DOOMED 0x1000000 |
| @@ -40,32 +40,28 @@ extern struct nsproxy init_nsproxy; · 39 unmodified lines | ||
| 40 | 40 | * the namespaces access rules are: |
| 41 | 41 | * |
| 42 | 42 | * 1. only current task is allowed to change tsk->nsproxy pointer or |
| 43 | − * any pointer on the nsproxy itself | |
| 43 | + * any pointer on the nsproxy itself. Current must hold the task_lock | |
| 44 | + * when changing tsk->nsproxy. | |
| 44 | 45 | * |
| 45 | 46 | * 2. when accessing (i.e. reading) current task's namespaces - no |
| 46 | 47 | * precautions should be taken - just dereference the pointers |
| 47 | 48 | * |
| 48 | 49 | * 3. the access to other task namespaces is performed like this |
| 49 | − * rcu_read_lock(); | |
| 50 | − * nsproxy = task_nsproxy(tsk); | |
| 50 | + * task_lock(task); | |
| 51 | + * nsproxy = task->nsproxy; | |
| 51 | 52 | * if (nsproxy != NULL) { |
| 52 | 53 | * / * |
| 53 | 54 | * * work with the namespaces here |
| 54 | 55 | * * e.g. get the reference on one of them |
| 55 | 56 | * * / |
| 56 | 57 | * } / * |
| 57 | − * * NULL task_nsproxy() means that this task is | |
| 58 | + * * NULL task->nsproxy means that this task is | |
| 58 | 59 | * * almost dead (zombie) |
| 59 | 60 | * * / |
| 60 | − * rcu_read_unlock(); | |
| 61 | + * task_unlock(task); | |
| 61 | 62 | * |
| 62 | 63 | */ |
| 63 | 64 | |
| 64 | −static inline struct nsproxy *task_nsproxy(struct task_struct *tsk) | |
| 65 | −{ | |
| 66 | − return rcu_dereference(tsk->nsproxy); | |
| 67 | −} | |
| 68 | − | |
| 69 | 65 | int copy_namespaces(unsigned long flags, struct task_struct *tsk); |
| 70 | 66 | void exit_task_namespaces(struct task_struct *tsk); |
| 71 | 67 | void switch_task_namespaces(struct task_struct *tsk, struct nsproxy *new); |
| @@ -33,6 +33,7 @@ struct pid_namespace { · 32 unmodified lines | ||
| 33 | 33 | #ifdef CONFIG_PROC_FS |
| 34 | 34 | struct vfsmount *proc_mnt; |
| 35 | 35 | struct dentry *proc_self; |
| 36 | + struct dentry *proc_thread_self; | |
| 36 | 37 | #endif |
| 37 | 38 | #ifdef CONFIG_BSD_PROCESS_ACCT |
| 38 | 39 | struct bsd_acct_struct *bacct; |
| @@ -154,11 +154,11 @@ static void *ipcns_get(struct task_struct *task) · 153 unmodified lines | ||
| 154 | 154 | struct ipc_namespace *ns = NULL; |
| 155 | 155 | struct nsproxy *nsproxy; |
| 156 | 156 | |
| 157 | − rcu_read_lock(); | |
| 158 | − nsproxy = task_nsproxy(task); | |
| 157 | + task_lock(task); | |
| 158 | + nsproxy = task->nsproxy; | |
| 159 | 159 | if (nsproxy) |
| 160 | 160 | ns = get_ipc_ns(nsproxy->ipc_ns); |
| 161 | − rcu_read_unlock(); | |
| 161 | + task_unlock(task); | |
| 162 | 162 | |
| 163 | 163 | return ns; |
| 164 | 164 | } |
| @@ -204,20 +204,13 @@ void switch_task_namespaces(struct task_struct *p, struct nsproxy *new) · 203 unmodified lines | ||
| 204 | 204 | |
| 205 | 205 | might_sleep(); |
| 206 | 206 | |
| 207 | + task_lock(p); | |
| 207 | 208 | ns = p->nsproxy; |
| 209 | + p->nsproxy = new; | |
| 210 | + task_unlock(p); | |
| 208 | 211 | |
| 209 | − rcu_assign_pointer(p->nsproxy, new); | |
| 210 | − | |
| 211 | − if (ns && atomic_dec_and_test(&ns->count)) { | |
| 212 | − /* | |
| 213 | − * wait for others to get what they want from this nsproxy. | |
| 214 | − * | |
| 215 | − * cannot release this nsproxy via the call_rcu() since | |
| 216 | − * put_mnt_ns() will want to sleep | |
| 217 | − */ | |
| 218 | − synchronize_rcu(); | |
| 212 | + if (ns && atomic_dec_and_test(&ns->count)) | |
| 219 | 213 | free_nsproxy(ns); |
| 220 | − } | |
| 221 | 214 | } |
| 222 | 215 | |
| 223 | 216 | void exit_task_namespaces(struct task_struct *p) |
| @@ -93,13 +93,13 @@ static void *utsns_get(struct task_struct *task) · 92 unmodified lines | ||
| 93 | 93 | struct uts_namespace *ns = NULL; |
| 94 | 94 | struct nsproxy *nsproxy; |
| 95 | 95 | |
| 96 | − rcu_read_lock(); | |
| 97 | − nsproxy = task_nsproxy(task); | |
| 96 | + task_lock(task); | |
| 97 | + nsproxy = task->nsproxy; | |
| 98 | 98 | if (nsproxy) { |
| 99 | 99 | ns = nsproxy->uts_ns; |
| 100 | 100 | get_uts_ns(ns); |
| 101 | 101 | } |
| 102 | − rcu_read_unlock(); | |
| 102 | + task_unlock(task); | |
| 103 | 103 | |
| 104 | 104 | return ns; |
| 105 | 105 | } |
| @@ -373,9 +373,11 @@ struct net *get_net_ns_by_pid(pid_t pid) · 372 unmodified lines | ||
| 373 | 373 | tsk = find_task_by_vpid(pid); |
| 374 | 374 | if (tsk) { |
| 375 | 375 | struct nsproxy *nsproxy; |
| 376 | − nsproxy = task_nsproxy(tsk); | |
| 376 | + task_lock(tsk); | |
| 377 | + nsproxy = tsk->nsproxy; | |
| 377 | 378 | if (nsproxy) |
| 378 | 379 | net = get_net(nsproxy->net_ns); |
| 380 | + task_unlock(tsk); | |
| 379 | 381 | } |
| 380 | 382 | rcu_read_unlock(); |
| 381 | 383 | return net; |
| @@ -632,11 +634,11 @@ static void *netns_get(struct task_struct *task) · 250 unmodified lines | ||
| 632 | 634 | struct net *net = NULL; |
| 633 | 635 | struct nsproxy *nsproxy; |
| 634 | 636 | |
| 635 | − rcu_read_lock(); | |
| 636 | − nsproxy = task_nsproxy(task); | |
| 637 | + task_lock(task); | |
| 638 | + nsproxy = task->nsproxy; | |
| 637 | 639 | if (nsproxy) |
| 638 | 640 | net = get_net(nsproxy->net_ns); |
| 639 | − rcu_read_unlock(); | |
| 641 | + task_unlock(task); | |
| 640 | 642 | |
| 641 | 643 | return net; |
| 642 | 644 | } |
| @@ -5,6 +5,7 @@ TARGETS += kcmp · 4 unmodified lines | ||
| 5 | 5 | TARGETS += memfd |
| 6 | 6 | TARGETS += memory-hotplug |
| 7 | 7 | TARGETS += mqueue |
| 8 | +TARGETS += mount | |
| 8 | 9 | TARGETS += net |
| 9 | 10 | TARGETS += ptrace |
| 10 | 11 | TARGETS += timers |
| @@ -0,0 +1,17 @@ | ||
| 1 | +# Makefile for mount selftests. | |
| 2 | + | |
| 3 | +all: unprivileged-remount-test | |
| 4 | + | |
| 5 | +unprivileged-remount-test: unprivileged-remount-test.c | |
| 6 | + gcc -Wall -O2 unprivileged-remount-test.c -o unprivileged-remount-test | |
| 7 | + | |
| 8 | +# Allow specific tests to be selected. | |
| 9 | +test_unprivileged_remount: unprivileged-remount-test | |
| 10 | + @if [ -f /proc/self/uid_map ] ; then ./unprivileged-remount-test ; fi | |
| 11 | + | |
| 12 | +run_tests: all test_unprivileged_remount | |
| 13 | + | |
| 14 | +clean: | |
| 15 | + rm -f unprivileged-remount-test | |
| 16 | + | |
| 17 | +.PHONY: all test_unprivileged_remount | |
| @@ -0,0 +1,242 @@ | ||
| 1 | +#define _GNU_SOURCE | |
| 2 | +#include <sched.h> | |
| 3 | +#include <stdio.h> | |
| 4 | +#include <errno.h> | |
| 5 | +#include <string.h> | |
| 6 | +#include <sys/types.h> | |
| 7 | +#include <sys/mount.h> | |
| 8 | +#include <sys/wait.h> | |
| 9 | +#include <stdlib.h> | |
| 10 | +#include <unistd.h> | |
| 11 | +#include <fcntl.h> | |
| 12 | +#include <grp.h> | |
| 13 | +#include <stdbool.h> | |
| 14 | +#include <stdarg.h> | |
| 15 | + | |
| 16 | +#ifndef CLONE_NEWNS | |
| 17 | +# define CLONE_NEWNS 0x00020000 | |
| 18 | +#endif | |
| 19 | +#ifndef CLONE_NEWUTS | |
| 20 | +# define CLONE_NEWUTS 0x04000000 | |
| 21 | +#endif | |
| 22 | +#ifndef CLONE_NEWIPC | |
| 23 | +# define CLONE_NEWIPC 0x08000000 | |
| 24 | +#endif | |
| 25 | +#ifndef CLONE_NEWNET | |
| 26 | +# define CLONE_NEWNET 0x40000000 | |
| 27 | +#endif | |
| 28 | +#ifndef CLONE_NEWUSER | |
| 29 | +# define CLONE_NEWUSER 0x10000000 | |
| 30 | +#endif | |
| 31 | +#ifndef CLONE_NEWPID | |
| 32 | +# define CLONE_NEWPID 0x20000000 | |
| 33 | +#endif | |
| 34 | + | |
| 35 | +#ifndef MS_RELATIME | |
| 36 | +#define MS_RELATIME (1 << 21) | |
| 37 | +#endif | |
| 38 | +#ifndef MS_STRICTATIME | |
| 39 | +#define MS_STRICTATIME (1 << 24) | |
| 40 | +#endif | |
| 41 | + | |
| 42 | +static void die(char *fmt, ...) | |
| 43 | +{ | |
| 44 | + va_list ap; | |
| 45 | + va_start(ap, fmt); | |
| 46 | + vfprintf(stderr, fmt, ap); | |
| 47 | + va_end(ap); | |
| 48 | + exit(EXIT_FAILURE); | |
| 49 | +} | |
| 50 | + | |
| 51 | +static void write_file(char *filename, char *fmt, ...) | |
| 52 | +{ | |
| 53 | + char buf[4096]; | |
| 54 | + int fd; | |
| 55 | + ssize_t written; | |
| 56 | + int buf_len; | |
| 57 | + va_list ap; | |
| 58 | + | |
| 59 | + va_start(ap, fmt); | |
| 60 | + buf_len = vsnprintf(buf, sizeof(buf), fmt, ap); | |
| 61 | + va_end(ap); | |
| 62 | + if (buf_len < 0) { | |
| 63 | + die("vsnprintf failed: %s\n", | |
| 64 | + strerror(errno)); | |
| 65 | + } | |
| 66 | + if (buf_len >= sizeof(buf)) { | |
| 67 | + die("vsnprintf output truncated\n"); | |
| 68 | + } | |
| 69 | + | |
| 70 | + fd = open(filename, O_WRONLY); | |
| 71 | + if (fd < 0) { | |
| 72 | + die("open of %s failed: %s\n", | |
| 73 | + filename, strerror(errno)); | |
| 74 | + } | |
| 75 | + written = write(fd, buf, buf_len); | |
| 76 | + if (written != buf_len) { | |
| 77 | + if (written >= 0) { | |
| 78 | + die("short write to %s\n", filename); | |
| 79 | + } else { | |
| 80 | + die("write to %s failed: %s\n", | |
| 81 | + filename, strerror(errno)); | |
| 82 | + } | |
| 83 | + } | |
| 84 | + if (close(fd) != 0) { | |
| 85 | + die("close of %s failed: %s\n", | |
| 86 | + filename, strerror(errno)); | |
| 87 | + } | |
| 88 | +} | |
| 89 | + | |
| 90 | +static void create_and_enter_userns(void) | |
| 91 | +{ | |
| 92 | + uid_t uid; | |
| 93 | + gid_t gid; | |
| 94 | + | |
| 95 | + uid = getuid(); | |
| 96 | + gid = getgid(); | |
| 97 | + | |
| 98 | + if (unshare(CLONE_NEWUSER) !=0) { | |
| 99 | + die("unshare(CLONE_NEWUSER) failed: %s\n", | |
| 100 | + strerror(errno)); | |
| 101 | + } | |
| 102 | + | |
| 103 | + write_file("/proc/self/uid_map", "0 %d 1", uid); | |
| 104 | + write_file("/proc/self/gid_map", "0 %d 1", gid); | |
| 105 | + | |
| 106 | + if (setgroups(0, NULL) != 0) { | |
| 107 | + die("setgroups failed: %s\n", | |
| 108 | + strerror(errno)); | |
| 109 | + } | |
| 110 | + if (setgid(0) != 0) { | |
| 111 | + die ("setgid(0) failed %s\n", | |
| 112 | + strerror(errno)); | |
| 113 | + } | |
| 114 | + if (setuid(0) != 0) { | |
| 115 | + die("setuid(0) failed %s\n", | |
| 116 | + strerror(errno)); | |
| 117 | + } | |
| 118 | +} | |
| 119 | + | |
| 120 | +static | |
| 121 | +bool test_unpriv_remount(int mount_flags, int remount_flags, int invalid_flags) | |
| 122 | +{ | |
| 123 | + pid_t child; | |
| 124 | + | |
| 125 | + child = fork(); | |
| 126 | + if (child == -1) { | |
| 127 | + die("fork failed: %s\n", | |
| 128 | + strerror(errno)); | |
| 129 | + } | |
| 130 | + if (child != 0) { /* parent */ | |
| 131 | + pid_t pid; | |
| 132 | + int status; | |
| 133 | + pid = waitpid(child, &status, 0); | |
| 134 | + if (pid == -1) { | |
| 135 | + die("waitpid failed: %s\n", | |
| 136 | + strerror(errno)); | |
| 137 | + } | |
| 138 | + if (pid != child) { | |
| 139 | + die("waited for %d got %d\n", | |
| 140 | + child, pid); | |
| 141 | + } | |
| 142 | + if (!WIFEXITED(status)) { | |
| 143 | + die("child did not terminate cleanly\n"); | |
| 144 | + } | |
| 145 | + return WEXITSTATUS(status) == EXIT_SUCCESS ? true : false; | |
| 146 | + } | |
| 147 | + | |
| 148 | + create_and_enter_userns(); | |
| 149 | + if (unshare(CLONE_NEWNS) != 0) { | |
| 150 | + die("unshare(CLONE_NEWNS) failed: %s\n", | |
| 151 | + strerror(errno)); | |
| 152 | + } | |
| 153 | + | |
| 154 | + if (mount("testing", "/tmp", "ramfs", mount_flags, NULL) != 0) { | |
| 155 | + die("mount of /tmp failed: %s\n", | |
| 156 | + strerror(errno)); | |
| 157 | + } | |
| 158 | + | |
| 159 | + create_and_enter_userns(); | |
| 160 | + | |
| 161 | + if (unshare(CLONE_NEWNS) != 0) { | |
| 162 | + die("unshare(CLONE_NEWNS) failed: %s\n", | |
| 163 | + strerror(errno)); | |
| 164 | + } | |
| 165 | + | |
| 166 | + if (mount("/tmp", "/tmp", "none", | |
| 167 | + MS_REMOUNT | MS_BIND | remount_flags, NULL) != 0) { | |
| 168 | + /* system("cat /proc/self/mounts"); */ | |
| 169 | + die("remount of /tmp failed: %s\n", | |
| 170 | + strerror(errno)); | |
| 171 | + } | |
| 172 | + | |
| 173 | + if (mount("/tmp", "/tmp", "none", | |
| 174 | + MS_REMOUNT | MS_BIND | invalid_flags, NULL) == 0) { | |
| 175 | + /* system("cat /proc/self/mounts"); */ | |
| 176 | + die("remount of /tmp with invalid flags " | |
| 177 | + "succeeded unexpectedly\n"); | |
| 178 | + } | |
| 179 | + exit(EXIT_SUCCESS); | |
| 180 | +} | |
| 181 | + | |
| 182 | +static bool test_unpriv_remount_simple(int mount_flags) | |
| 183 | +{ | |
| 184 | + return test_unpriv_remount(mount_flags, mount_flags, 0); | |
| 185 | +} | |
| 186 | + | |
| 187 | +static bool test_unpriv_remount_atime(int mount_flags, int invalid_flags) | |
| 188 | +{ | |
| 189 | + return test_unpriv_remount(mount_flags, mount_flags, invalid_flags); | |
| 190 | +} | |
| 191 | + | |
| 192 | +int main(int argc, char **argv) | |
| 193 | +{ | |
| 194 | + if (!test_unpriv_remount_simple(MS_RDONLY|MS_NODEV)) { | |
| 195 | + die("MS_RDONLY malfunctions\n"); | |
| 196 | + } | |
| 197 | + if (!test_unpriv_remount_simple(MS_NODEV)) { | |
| 198 | + die("MS_NODEV malfunctions\n"); | |
| 199 | + } | |
| 200 | + if (!test_unpriv_remount_simple(MS_NOSUID|MS_NODEV)) { | |
| 201 | + die("MS_NOSUID malfunctions\n"); | |
| 202 | + } | |
| 203 | + if (!test_unpriv_remount_simple(MS_NOEXEC|MS_NODEV)) { | |
| 204 | + die("MS_NOEXEC malfunctions\n"); | |
| 205 | + } | |
| 206 | + if (!test_unpriv_remount_atime(MS_RELATIME|MS_NODEV, | |
| 207 | + MS_NOATIME|MS_NODEV)) | |
| 208 | + { | |
| 209 | + die("MS_RELATIME malfunctions\n"); | |
| 210 | + } | |
| 211 | + if (!test_unpriv_remount_atime(MS_STRICTATIME|MS_NODEV, | |
| 212 | + MS_NOATIME|MS_NODEV)) | |
| 213 | + { | |
| 214 | + die("MS_STRICTATIME malfunctions\n"); | |
| 215 | + } | |
| 216 | + if (!test_unpriv_remount_atime(MS_NOATIME|MS_NODEV, | |
| 217 | + MS_STRICTATIME|MS_NODEV)) | |
| 218 | + { | |
| 219 | + die("MS_RELATIME malfunctions\n"); | |
| 220 | + } | |
| 221 | + if (!test_unpriv_remount_atime(MS_RELATIME|MS_NODIRATIME|MS_NODEV, | |
| 222 | + MS_NOATIME|MS_NODEV)) | |
| 223 | + { | |
| 224 | + die("MS_RELATIME malfunctions\n"); | |
| 225 | + } | |
| 226 | + if (!test_unpriv_remount_atime(MS_STRICTATIME|MS_NODIRATIME|MS_NODEV, | |
| 227 | + MS_NOATIME|MS_NODEV)) | |
| 228 | + { | |
| 229 | + die("MS_RELATIME malfunctions\n"); | |
| 230 | + } | |
| 231 | + if (!test_unpriv_remount_atime(MS_NOATIME|MS_NODIRATIME|MS_NODEV, | |
| 232 | + MS_STRICTATIME|MS_NODEV)) | |
| 233 | + { | |
| 234 | + die("MS_RELATIME malfunctions\n"); | |
| 235 | + } | |
| 236 | + if (!test_unpriv_remount(MS_STRICTATIME|MS_NODEV, MS_NODEV, | |
| 237 | + MS_NOATIME|MS_NODEV)) | |
| 238 | + { | |
| 239 | + die("Default atime malfunctions\n"); | |
| 240 | + } | |
| 241 | + return EXIT_SUCCESS; | |
| 242 | +} | |