shm.c 31.5 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * linux/ipc/shm.c
 * Copyright (C) 1992, 1993 Krishna Balasubramanian
 *	 Many improvements/fixes by Bruno Haible.
 * Replaced `struct shm_desc' by `struct vm_area_struct', July 1994.
 * Fixed the shm swap deallocation (shm_unuse()), August 1998 Andrea Arcangeli.
 *
 * /proc/sysvipc/shm support (c) 1999 Dragos Acostachioaie <dragos@iname.com>
 * BIGMEM support, Andrea Arcangeli <andrea@suse.de>
 * SMP thread shm, Jean-Luc Boyard <jean-luc.boyard@siemens.fr>
 * HIGHMEM support, Ingo Molnar <mingo@redhat.com>
 * Make shmmax, shmall, shmmni sysctl'able, Christoph Rohland <cr@sap.com>
 * Shared /dev/zero support, Kanoj Sarcar <kanoj@sgi.com>
 * Move the mm functionality over to mm/shmem.c, Christoph Rohland <cr@sap.com>
 *
Steve Grubb's avatar
Steve Grubb committed
16 17
 * support for audit of ipc object properties and permission changes
 * Dustin Kirkland <dustin.kirkland@us.ibm.com>
Kirill Korotaev's avatar
Kirill Korotaev committed
18 19 20 21
 *
 * namespaces support
 * OpenVZ, SWsoft Inc.
 * Pavel Emelianov <xemul@openvz.org>
22 23 24
 *
 * Better ipc lock (kern_ipc_perm.lock) handling
 * Davidlohr Bueso <davidlohr.bueso@hp.com>, June 2013.
Linus Torvalds's avatar
Linus Torvalds committed
25 26 27 28 29 30 31 32 33 34 35 36 37
 */

#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/hugetlb.h>
#include <linux/shm.h>
#include <linux/init.h>
#include <linux/file.h>
#include <linux/mman.h>
#include <linux/shmem_fs.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/audit.h>
38
#include <linux/capability.h>
39
#include <linux/ptrace.h>
40
#include <linux/seq_file.h>
Nadia Derbey's avatar
Nadia Derbey committed
41
#include <linux/rwsem.h>
Kirill Korotaev's avatar
Kirill Korotaev committed
42
#include <linux/nsproxy.h>
43
#include <linux/mount.h>
44
#include <linux/ipc_namespace.h>
45

Linus Torvalds's avatar
Linus Torvalds committed
46 47 48 49
#include <asm/uaccess.h>

#include "util.h"

50 51 52 53 54 55 56 57 58
struct shm_file_data {
	int id;
	struct ipc_namespace *ns;
	struct file *file;
	const struct vm_operations_struct *vm_ops;
};

#define shm_file_data(file) (*((struct shm_file_data **)&(file)->private_data))

59
static const struct file_operations shm_file_operations;
60
static const struct vm_operations_struct shm_vm_ops;
Linus Torvalds's avatar
Linus Torvalds committed
61

62
#define shm_ids(ns)	((ns)->ids[IPC_SHM_IDS])
Linus Torvalds's avatar
Linus Torvalds committed
63

Kirill Korotaev's avatar
Kirill Korotaev committed
64 65
#define shm_unlock(shp)			\
	ipc_unlock(&(shp)->shm_perm)
Linus Torvalds's avatar
Linus Torvalds committed
66

Nadia Derbey's avatar
Nadia Derbey committed
67
static int newseg(struct ipc_namespace *, struct ipc_params *);
68 69
static void shm_open(struct vm_area_struct *vma);
static void shm_close(struct vm_area_struct *vma);
Kirill Korotaev's avatar
Kirill Korotaev committed
70
static void shm_destroy (struct ipc_namespace *ns, struct shmid_kernel *shp);
Linus Torvalds's avatar
Linus Torvalds committed
71
#ifdef CONFIG_PROC_FS
72
static int sysvipc_shm_proc_show(struct seq_file *s, void *it);
Linus Torvalds's avatar
Linus Torvalds committed
73 74
#endif

75
void shm_init_ns(struct ipc_namespace *ns)
Kirill Korotaev's avatar
Kirill Korotaev committed
76 77 78 79
{
	ns->shm_ctlmax = SHMMAX;
	ns->shm_ctlall = SHMALL;
	ns->shm_ctlmni = SHMMNI;
80
	ns->shm_rmid_forced = 0;
Kirill Korotaev's avatar
Kirill Korotaev committed
81
	ns->shm_tot = 0;
WANG Cong's avatar
WANG Cong committed
82
	ipc_init_ids(&shm_ids(ns));
Kirill Korotaev's avatar
Kirill Korotaev committed
83 84
}

Nadia Derbey's avatar
Nadia Derbey committed
85
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
86 87
 * Called with shm_ids.rwsem (writer) and the shp structure locked.
 * Only shm_ids.rwsem remains locked on exit.
Nadia Derbey's avatar
Nadia Derbey committed
88
 */
89
static void do_shm_rmid(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp)
Kirill Korotaev's avatar
Kirill Korotaev committed
90
{
91 92 93
	struct shmid_kernel *shp;
	shp = container_of(ipcp, struct shmid_kernel, shm_perm);

Kirill Korotaev's avatar
Kirill Korotaev committed
94 95 96 97 98 99 100 101 102
	if (shp->shm_nattch){
		shp->shm_perm.mode |= SHM_DEST;
		/* Do not find it any more */
		shp->shm_perm.key = IPC_PRIVATE;
		shm_unlock(shp);
	} else
		shm_destroy(ns, shp);
}

103
#ifdef CONFIG_IPC_NS
Kirill Korotaev's avatar
Kirill Korotaev committed
104 105
void shm_exit_ns(struct ipc_namespace *ns)
{
106
	free_ipcs(ns, &shm_ids(ns), do_shm_rmid);
107
	idr_destroy(&ns->ids[IPC_SHM_IDS].ipcs_idr);
Kirill Korotaev's avatar
Kirill Korotaev committed
108
}
109
#endif
Linus Torvalds's avatar
Linus Torvalds committed
110

111
static int __init ipc_ns_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
112
{
113
	shm_init_ns(&init_ipc_ns);
114 115 116 117 118 119 120
	return 0;
}

pure_initcall(ipc_ns_init);

void __init shm_init (void)
{
121
	ipc_init_proc_interface("sysvipc/shm",
122 123 124 125 126
#if BITS_PER_LONG <= 32
				"       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime        rss       swap\n",
#else
				"       key      shmid perms                  size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime                   rss                  swap\n",
#endif
Kirill Korotaev's avatar
Kirill Korotaev committed
127
				IPC_SHM_IDS, sysvipc_shm_proc_show);
Linus Torvalds's avatar
Linus Torvalds committed
128 129
}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
static inline struct shmid_kernel *shm_obtain_object(struct ipc_namespace *ns, int id)
{
	struct kern_ipc_perm *ipcp = ipc_obtain_object(&shm_ids(ns), id);

	if (IS_ERR(ipcp))
		return ERR_CAST(ipcp);

	return container_of(ipcp, struct shmid_kernel, shm_perm);
}

static inline struct shmid_kernel *shm_obtain_object_check(struct ipc_namespace *ns, int id)
{
	struct kern_ipc_perm *ipcp = ipc_obtain_object_check(&shm_ids(ns), id);

	if (IS_ERR(ipcp))
		return ERR_CAST(ipcp);

	return container_of(ipcp, struct shmid_kernel, shm_perm);
}

Nadia Derbey's avatar
Nadia Derbey committed
150
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
151
 * shm_lock_(check_) routines are called in the paths where the rwsem
Nadia Derbey's avatar
Nadia Derbey committed
152
 * is not necessarily held.
Nadia Derbey's avatar
Nadia Derbey committed
153
 */
154
static inline struct shmid_kernel *shm_lock(struct ipc_namespace *ns, int id)
Linus Torvalds's avatar
Linus Torvalds committed
155
{
Nadia Derbey's avatar
Nadia Derbey committed
156 157
	struct kern_ipc_perm *ipcp = ipc_lock(&shm_ids(ns), id);

158 159 160
	if (IS_ERR(ipcp))
		return (struct shmid_kernel *)ipcp;

Nadia Derbey's avatar
Nadia Derbey committed
161
	return container_of(ipcp, struct shmid_kernel, shm_perm);
162 163
}

164 165 166
static inline void shm_lock_by_ptr(struct shmid_kernel *ipcp)
{
	rcu_read_lock();
167
	ipc_lock_object(&ipcp->shm_perm);
168 169
}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
170 171 172 173 174 175 176 177 178
static void shm_rcu_free(struct rcu_head *head)
{
	struct ipc_rcu *p = container_of(head, struct ipc_rcu, rcu);
	struct shmid_kernel *shp = ipc_rcu_to_struct(p);

	security_shm_free(shp);
	ipc_rcu_free(head);
}

Nadia Derbey's avatar
Nadia Derbey committed
179
static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s)
Linus Torvalds's avatar
Linus Torvalds committed
180
{
Nadia Derbey's avatar
Nadia Derbey committed
181
	ipc_rmid(&shm_ids(ns), &s->shm_perm);
Linus Torvalds's avatar
Linus Torvalds committed
182 183 184
}


185 186
/* This is called by fork, once for every shm attach. */
static void shm_open(struct vm_area_struct *vma)
Kirill Korotaev's avatar
Kirill Korotaev committed
187
{
188 189
	struct file *file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);
Linus Torvalds's avatar
Linus Torvalds committed
190 191
	struct shmid_kernel *shp;

192
	shp = shm_lock(sfd->ns, sfd->id);
193
	BUG_ON(IS_ERR(shp));
Linus Torvalds's avatar
Linus Torvalds committed
194
	shp->shm_atim = get_seconds();
195
	shp->shm_lprid = task_tgid_vnr(current);
Linus Torvalds's avatar
Linus Torvalds committed
196 197 198 199 200 201 202
	shp->shm_nattch++;
	shm_unlock(shp);
}

/*
 * shm_destroy - free the struct shmid_kernel
 *
Nadia Derbey's avatar
Nadia Derbey committed
203
 * @ns: namespace
Linus Torvalds's avatar
Linus Torvalds committed
204 205
 * @shp: struct to free
 *
Davidlohr Bueso's avatar
Davidlohr Bueso committed
206
 * It has to be called with shp and shm_ids.rwsem (writer) locked,
Linus Torvalds's avatar
Linus Torvalds committed
207 208
 * but returns with shp unlocked and freed.
 */
Kirill Korotaev's avatar
Kirill Korotaev committed
209
static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)
Linus Torvalds's avatar
Linus Torvalds committed
210
{
Kirill Korotaev's avatar
Kirill Korotaev committed
211
	ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT;
Nadia Derbey's avatar
Nadia Derbey committed
212
	shm_rmid(ns, shp);
Linus Torvalds's avatar
Linus Torvalds committed
213 214 215
	shm_unlock(shp);
	if (!is_file_hugepages(shp->shm_file))
		shmem_lock(shp->shm_file, 0, shp->mlock_user);
216
	else if (shp->mlock_user)
Al Viro's avatar
Al Viro committed
217
		user_shm_unlock(file_inode(shp->shm_file)->i_size,
Linus Torvalds's avatar
Linus Torvalds committed
218 219
						shp->mlock_user);
	fput (shp->shm_file);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
220
	ipc_rcu_putref(shp, shm_rcu_free);
Linus Torvalds's avatar
Linus Torvalds committed
221 222
}

223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
/*
 * shm_may_destroy - identifies whether shm segment should be destroyed now
 *
 * Returns true if and only if there are no active users of the segment and
 * one of the following is true:
 *
 * 1) shmctl(id, IPC_RMID, NULL) was called for this shp
 *
 * 2) sysctl kernel.shm_rmid_forced is set to 1.
 */
static bool shm_may_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)
{
	return (shp->shm_nattch == 0) &&
	       (ns->shm_rmid_forced ||
		(shp->shm_perm.mode & SHM_DEST));
}

Linus Torvalds's avatar
Linus Torvalds committed
240
/*
241
 * remove the attach descriptor vma.
Linus Torvalds's avatar
Linus Torvalds committed
242 243 244 245
 * free memory for segment if it is marked destroyed.
 * The descriptor has already been removed from the current->mm->mmap list
 * and will later be kfree()d.
 */
246
static void shm_close(struct vm_area_struct *vma)
Linus Torvalds's avatar
Linus Torvalds committed
247
{
248 249
	struct file * file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);
Linus Torvalds's avatar
Linus Torvalds committed
250
	struct shmid_kernel *shp;
251
	struct ipc_namespace *ns = sfd->ns;
Kirill Korotaev's avatar
Kirill Korotaev committed
252

Davidlohr Bueso's avatar
Davidlohr Bueso committed
253
	down_write(&shm_ids(ns).rwsem);
Linus Torvalds's avatar
Linus Torvalds committed
254
	/* remove from the list of attaches of the shm segment */
Nadia Derbey's avatar
Nadia Derbey committed
255
	shp = shm_lock(ns, sfd->id);
256
	BUG_ON(IS_ERR(shp));
257
	shp->shm_lprid = task_tgid_vnr(current);
Linus Torvalds's avatar
Linus Torvalds committed
258 259
	shp->shm_dtim = get_seconds();
	shp->shm_nattch--;
260 261 262 263
	if (shm_may_destroy(ns, shp))
		shm_destroy(ns, shp);
	else
		shm_unlock(shp);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
264
	up_write(&shm_ids(ns).rwsem);
265 266
}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
267
/* Called with ns->shm_ids(ns).rwsem locked */
268 269 270
static int shm_try_destroy_current(int id, void *p, void *data)
{
	struct ipc_namespace *ns = data;
271 272
	struct kern_ipc_perm *ipcp = p;
	struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm);
273

274
	if (shp->shm_creator != current)
275 276 277 278 279 280 281 282 283 284 285 286 287
		return 0;

	/*
	 * Mark it as orphaned to destroy the segment when
	 * kernel.shm_rmid_forced is changed.
	 * It is noop if the following shm_may_destroy() returns true.
	 */
	shp->shm_creator = NULL;

	/*
	 * Don't even try to destroy it.  If shm_rmid_forced=0 and IPC_RMID
	 * is not set, it shouldn't be deleted here.
	 */
288
	if (!ns->shm_rmid_forced)
289 290
		return 0;

291 292
	if (shm_may_destroy(ns, shp)) {
		shm_lock_by_ptr(shp);
293
		shm_destroy(ns, shp);
294
	}
295 296 297
	return 0;
}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
298
/* Called with ns->shm_ids(ns).rwsem locked */
299 300 301
static int shm_try_destroy_orphaned(int id, void *p, void *data)
{
	struct ipc_namespace *ns = data;
302 303
	struct kern_ipc_perm *ipcp = p;
	struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm);
304 305 306 307

	/*
	 * We want to destroy segments without users and with already
	 * exit'ed originating process.
308
	 *
Davidlohr Bueso's avatar
Davidlohr Bueso committed
309
	 * As shp->* are changed under rwsem, it's safe to skip shp locking.
310
	 */
311
	if (shp->shm_creator != NULL)
312 313
		return 0;

314 315
	if (shm_may_destroy(ns, shp)) {
		shm_lock_by_ptr(shp);
Kirill Korotaev's avatar
Kirill Korotaev committed
316
		shm_destroy(ns, shp);
317
	}
318 319 320 321 322
	return 0;
}

void shm_destroy_orphaned(struct ipc_namespace *ns)
{
Davidlohr Bueso's avatar
Davidlohr Bueso committed
323
	down_write(&shm_ids(ns).rwsem);
Vasiliy Kulikov's avatar
Vasiliy Kulikov committed
324
	if (shm_ids(ns).in_use)
325
		idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_orphaned, ns);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
326
	up_write(&shm_ids(ns).rwsem);
327 328 329 330 331
}


void exit_shm(struct task_struct *task)
{
332
	struct ipc_namespace *ns = task->nsproxy->ipc_ns;
333

Vasiliy Kulikov's avatar
Vasiliy Kulikov committed
334 335 336
	if (shm_ids(ns).in_use == 0)
		return;

337
	/* Destroy all already created segments, but not mapped yet */
Davidlohr Bueso's avatar
Davidlohr Bueso committed
338
	down_write(&shm_ids(ns).rwsem);
Vasiliy Kulikov's avatar
Vasiliy Kulikov committed
339
	if (shm_ids(ns).in_use)
340
		idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_current, ns);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
341
	up_write(&shm_ids(ns).rwsem);
Linus Torvalds's avatar
Linus Torvalds committed
342 343
}

Nick Piggin's avatar
Nick Piggin committed
344
static int shm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
345 346 347 348
{
	struct file *file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);

Nick Piggin's avatar
Nick Piggin committed
349
	return sfd->vm_ops->fault(vma, vmf);
350 351 352
}

#ifdef CONFIG_NUMA
353
static int shm_set_policy(struct vm_area_struct *vma, struct mempolicy *new)
354 355 356 357 358 359 360 361 362
{
	struct file *file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);
	int err = 0;
	if (sfd->vm_ops->set_policy)
		err = sfd->vm_ops->set_policy(vma, new);
	return err;
}

363 364
static struct mempolicy *shm_get_policy(struct vm_area_struct *vma,
					unsigned long addr)
365 366 367 368 369 370 371
{
	struct file *file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);
	struct mempolicy *pol = NULL;

	if (sfd->vm_ops->get_policy)
		pol = sfd->vm_ops->get_policy(vma, addr);
372
	else if (vma->vm_policy)
373
		pol = vma->vm_policy;
374

375 376 377 378
	return pol;
}
#endif

Linus Torvalds's avatar
Linus Torvalds committed
379 380
static int shm_mmap(struct file * file, struct vm_area_struct * vma)
{
381
	struct shm_file_data *sfd = shm_file_data(file);
382 383
	int ret;

384 385 386 387
	ret = sfd->file->f_op->mmap(sfd->file, vma);
	if (ret != 0)
		return ret;
	sfd->vm_ops = vma->vm_ops;
David Howells's avatar
David Howells committed
388
#ifdef CONFIG_MMU
389
	BUG_ON(!sfd->vm_ops->fault);
David Howells's avatar
David Howells committed
390
#endif
391 392
	vma->vm_ops = &shm_vm_ops;
	shm_open(vma);
393 394

	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
395 396
}

Kirill Korotaev's avatar
Kirill Korotaev committed
397 398
static int shm_release(struct inode *ino, struct file *file)
{
399
	struct shm_file_data *sfd = shm_file_data(file);
Kirill Korotaev's avatar
Kirill Korotaev committed
400

401 402 403
	put_ipc_ns(sfd->ns);
	shm_file_data(file) = NULL;
	kfree(sfd);
Kirill Korotaev's avatar
Kirill Korotaev committed
404 405 406
	return 0;
}

407
static int shm_fsync(struct file *file, loff_t start, loff_t end, int datasync)
408 409 410
{
	struct shm_file_data *sfd = shm_file_data(file);

411 412
	if (!sfd->file->f_op->fsync)
		return -EINVAL;
413
	return sfd->file->f_op->fsync(sfd->file, start, end, datasync);
414 415
}

416 417 418 419 420 421 422 423 424 425
static long shm_fallocate(struct file *file, int mode, loff_t offset,
			  loff_t len)
{
	struct shm_file_data *sfd = shm_file_data(file);

	if (!sfd->file->f_op->fallocate)
		return -EOPNOTSUPP;
	return sfd->file->f_op->fallocate(file, mode, offset, len);
}

426 427 428 429 430
static unsigned long shm_get_unmapped_area(struct file *file,
	unsigned long addr, unsigned long len, unsigned long pgoff,
	unsigned long flags)
{
	struct shm_file_data *sfd = shm_file_data(file);
431 432
	return sfd->file->f_op->get_unmapped_area(sfd->file, addr, len,
						pgoff, flags);
433 434
}

435
static const struct file_operations shm_file_operations = {
Kirill Korotaev's avatar
Kirill Korotaev committed
436
	.mmap		= shm_mmap,
437
	.fsync		= shm_fsync,
Kirill Korotaev's avatar
Kirill Korotaev committed
438
	.release	= shm_release,
David Howells's avatar
David Howells committed
439 440 441
#ifndef CONFIG_MMU
	.get_unmapped_area	= shm_get_unmapped_area,
#endif
442
	.llseek		= noop_llseek,
443
	.fallocate	= shm_fallocate,
444 445 446 447 448 449
};

static const struct file_operations shm_file_operations_huge = {
	.mmap		= shm_mmap,
	.fsync		= shm_fsync,
	.release	= shm_release,
450
	.get_unmapped_area	= shm_get_unmapped_area,
451
	.llseek		= noop_llseek,
452
	.fallocate	= shm_fallocate,
Linus Torvalds's avatar
Linus Torvalds committed
453 454
};

455 456 457 458 459
int is_file_shm_hugepages(struct file *file)
{
	return file->f_op == &shm_file_operations_huge;
}

460
static const struct vm_operations_struct shm_vm_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
461 462
	.open	= shm_open,	/* callback for a new vm-area open */
	.close	= shm_close,	/* callback for when the vm-area is released */
463
	.fault	= shm_fault,
464 465 466
#if defined(CONFIG_NUMA)
	.set_policy = shm_set_policy,
	.get_policy = shm_get_policy,
Linus Torvalds's avatar
Linus Torvalds committed
467 468 469
#endif
};

Nadia Derbey's avatar
Nadia Derbey committed
470 471 472 473 474
/**
 * newseg - Create a new shared memory segment
 * @ns: namespace
 * @params: ptr to the structure that contains key, size and shmflg
 *
Davidlohr Bueso's avatar
Davidlohr Bueso committed
475
 * Called with shm_ids.rwsem held as a writer.
Nadia Derbey's avatar
Nadia Derbey committed
476 477
 */

Nadia Derbey's avatar
Nadia Derbey committed
478
static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
Linus Torvalds's avatar
Linus Torvalds committed
479
{
Nadia Derbey's avatar
Nadia Derbey committed
480 481 482
	key_t key = params->key;
	int shmflg = params->flg;
	size_t size = params->u.size;
Linus Torvalds's avatar
Linus Torvalds committed
483 484
	int error;
	struct shmid_kernel *shp;
485
	size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
Linus Torvalds's avatar
Linus Torvalds committed
486 487 488
	struct file * file;
	char name[13];
	int id;
489
	vm_flags_t acctflag = 0;
Linus Torvalds's avatar
Linus Torvalds committed
490

Kirill Korotaev's avatar
Kirill Korotaev committed
491
	if (size < SHMMIN || size > ns->shm_ctlmax)
Linus Torvalds's avatar
Linus Torvalds committed
492 493
		return -EINVAL;

494
	if (ns->shm_tot + numpages > ns->shm_ctlall)
Linus Torvalds's avatar
Linus Torvalds committed
495 496 497 498 499 500 501
		return -ENOSPC;

	shp = ipc_rcu_alloc(sizeof(*shp));
	if (!shp)
		return -ENOMEM;

	shp->shm_perm.key = key;
Andrew Morton's avatar
Andrew Morton committed
502
	shp->shm_perm.mode = (shmflg & S_IRWXUGO);
Linus Torvalds's avatar
Linus Torvalds committed
503 504 505 506 507
	shp->mlock_user = NULL;

	shp->shm_perm.security = NULL;
	error = security_shm_alloc(shp);
	if (error) {
Davidlohr Bueso's avatar
Davidlohr Bueso committed
508
		ipc_rcu_putref(shp, ipc_rcu_free);
Linus Torvalds's avatar
Linus Torvalds committed
509 510 511
		return error;
	}

512
	sprintf (name, "SYSV%08x", key);
Linus Torvalds's avatar
Linus Torvalds committed
513
	if (shmflg & SHM_HUGETLB) {
514
		struct hstate *hs;
515 516
		size_t hugesize;

517
		hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
518 519 520 521 522
		if (!hs) {
			error = -EINVAL;
			goto no_file;
		}
		hugesize = ALIGN(size, huge_page_size(hs));
523

524 525 526
		/* hugetlb_file_setup applies strict accounting */
		if (shmflg & SHM_NORESERVE)
			acctflag = VM_NORESERVE;
527
		file = hugetlb_file_setup(name, hugesize, acctflag,
528 529
				  &shp->mlock_user, HUGETLB_SHMFS_INODE,
				(shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
Linus Torvalds's avatar
Linus Torvalds committed
530
	} else {
531 532 533 534 535 536
		/*
		 * Do not allow no accounting for OVERCOMMIT_NEVER, even
	 	 * if it's asked for.
		 */
		if  ((shmflg & SHM_NORESERVE) &&
				sysctl_overcommit_memory != OVERCOMMIT_NEVER)
537
			acctflag = VM_NORESERVE;
538
		file = shmem_file_setup(name, size, acctflag);
Linus Torvalds's avatar
Linus Torvalds committed
539 540 541 542 543
	}
	error = PTR_ERR(file);
	if (IS_ERR(file))
		goto no_file;

544
	id = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni);
545 546
	if (id < 0) {
		error = id;
Linus Torvalds's avatar
Linus Torvalds committed
547
		goto no_id;
548
	}
Linus Torvalds's avatar
Linus Torvalds committed
549

550
	shp->shm_cprid = task_tgid_vnr(current);
Linus Torvalds's avatar
Linus Torvalds committed
551 552 553 554 555 556
	shp->shm_lprid = 0;
	shp->shm_atim = shp->shm_dtim = 0;
	shp->shm_ctim = get_seconds();
	shp->shm_segsz = size;
	shp->shm_nattch = 0;
	shp->shm_file = file;
557
	shp->shm_creator = current;
558

559 560 561 562
	/*
	 * shmid gets reported as "inode#" in /proc/pid/maps.
	 * proc-ps tools use this. Changing this will break them.
	 */
Al Viro's avatar
Al Viro committed
563
	file_inode(file)->i_ino = shp->shm_perm.id;
564

Kirill Korotaev's avatar
Kirill Korotaev committed
565
	ns->shm_tot += numpages;
Nadia Derbey's avatar
Nadia Derbey committed
566
	error = shp->shm_perm.id;
567

568
	ipc_unlock_object(&shp->shm_perm);
569
	rcu_read_unlock();
Nadia Derbey's avatar
Nadia Derbey committed
570
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
571 572

no_id:
573
	if (is_file_hugepages(file) && shp->mlock_user)
574
		user_shm_unlock(size, shp->mlock_user);
Linus Torvalds's avatar
Linus Torvalds committed
575 576
	fput(file);
no_file:
Davidlohr Bueso's avatar
Davidlohr Bueso committed
577
	ipc_rcu_putref(shp, shm_rcu_free);
Linus Torvalds's avatar
Linus Torvalds committed
578 579 580
	return error;
}

Nadia Derbey's avatar
Nadia Derbey committed
581
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
582
 * Called with shm_ids.rwsem and ipcp locked.
Nadia Derbey's avatar
Nadia Derbey committed
583
 */
Nadia Derbey's avatar
Nadia Derbey committed
584
static inline int shm_security(struct kern_ipc_perm *ipcp, int shmflg)
Nadia Derbey's avatar
Nadia Derbey committed
585
{
Nadia Derbey's avatar
Nadia Derbey committed
586 587 588 589
	struct shmid_kernel *shp;

	shp = container_of(ipcp, struct shmid_kernel, shm_perm);
	return security_shm_associate(shp, shmflg);
Nadia Derbey's avatar
Nadia Derbey committed
590 591
}

Nadia Derbey's avatar
Nadia Derbey committed
592
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
593
 * Called with shm_ids.rwsem and ipcp locked.
Nadia Derbey's avatar
Nadia Derbey committed
594
 */
Nadia Derbey's avatar
Nadia Derbey committed
595 596
static inline int shm_more_checks(struct kern_ipc_perm *ipcp,
				struct ipc_params *params)
Nadia Derbey's avatar
Nadia Derbey committed
597
{
Nadia Derbey's avatar
Nadia Derbey committed
598 599 600 601
	struct shmid_kernel *shp;

	shp = container_of(ipcp, struct shmid_kernel, shm_perm);
	if (shp->shm_segsz < params->u.size)
Nadia Derbey's avatar
Nadia Derbey committed
602 603 604 605 606
		return -EINVAL;

	return 0;
}

607
SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg)
Linus Torvalds's avatar
Linus Torvalds committed
608
{
Kirill Korotaev's avatar
Kirill Korotaev committed
609
	struct ipc_namespace *ns;
Nadia Derbey's avatar
Nadia Derbey committed
610 611
	struct ipc_ops shm_ops;
	struct ipc_params shm_params;
Kirill Korotaev's avatar
Kirill Korotaev committed
612 613

	ns = current->nsproxy->ipc_ns;
Linus Torvalds's avatar
Linus Torvalds committed
614

Nadia Derbey's avatar
Nadia Derbey committed
615 616 617
	shm_ops.getnew = newseg;
	shm_ops.associate = shm_security;
	shm_ops.more_checks = shm_more_checks;
Nadia Derbey's avatar
Nadia Derbey committed
618

Nadia Derbey's avatar
Nadia Derbey committed
619 620 621
	shm_params.key = key;
	shm_params.flg = shmflg;
	shm_params.u.size = size;
Linus Torvalds's avatar
Linus Torvalds committed
622

Nadia Derbey's avatar
Nadia Derbey committed
623
	return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params);
Linus Torvalds's avatar
Linus Torvalds committed
624 625 626 627 628 629 630 631 632 633 634
}

static inline unsigned long copy_shmid_to_user(void __user *buf, struct shmid64_ds *in, int version)
{
	switch(version) {
	case IPC_64:
		return copy_to_user(buf, in, sizeof(*in));
	case IPC_OLD:
	    {
		struct shmid_ds out;

635
		memset(&out, 0, sizeof(out));
Linus Torvalds's avatar
Linus Torvalds committed
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
		ipc64_perm_to_ipc_perm(&in->shm_perm, &out.shm_perm);
		out.shm_segsz	= in->shm_segsz;
		out.shm_atime	= in->shm_atime;
		out.shm_dtime	= in->shm_dtime;
		out.shm_ctime	= in->shm_ctime;
		out.shm_cpid	= in->shm_cpid;
		out.shm_lpid	= in->shm_lpid;
		out.shm_nattch	= in->shm_nattch;

		return copy_to_user(buf, &out, sizeof(out));
	    }
	default:
		return -EINVAL;
	}
}

652 653
static inline unsigned long
copy_shmid_from_user(struct shmid64_ds *out, void __user *buf, int version)
Linus Torvalds's avatar
Linus Torvalds committed
654 655 656
{
	switch(version) {
	case IPC_64:
657
		if (copy_from_user(out, buf, sizeof(*out)))
Linus Torvalds's avatar
Linus Torvalds committed
658 659 660 661 662 663 664 665 666
			return -EFAULT;
		return 0;
	case IPC_OLD:
	    {
		struct shmid_ds tbuf_old;

		if (copy_from_user(&tbuf_old, buf, sizeof(tbuf_old)))
			return -EFAULT;

667 668 669
		out->shm_perm.uid	= tbuf_old.shm_perm.uid;
		out->shm_perm.gid	= tbuf_old.shm_perm.gid;
		out->shm_perm.mode	= tbuf_old.shm_perm.mode;
Linus Torvalds's avatar
Linus Torvalds committed
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703

		return 0;
	    }
	default:
		return -EINVAL;
	}
}

static inline unsigned long copy_shminfo_to_user(void __user *buf, struct shminfo64 *in, int version)
{
	switch(version) {
	case IPC_64:
		return copy_to_user(buf, in, sizeof(*in));
	case IPC_OLD:
	    {
		struct shminfo out;

		if(in->shmmax > INT_MAX)
			out.shmmax = INT_MAX;
		else
			out.shmmax = (int)in->shmmax;

		out.shmmin	= in->shmmin;
		out.shmmni	= in->shmmni;
		out.shmseg	= in->shmseg;
		out.shmall	= in->shmall; 

		return copy_to_user(buf, &out, sizeof(out));
	    }
	default:
		return -EINVAL;
	}
}

704 705
/*
 * Calculate and add used RSS and swap pages of a shm.
Davidlohr Bueso's avatar
Davidlohr Bueso committed
706
 * Called with shm_ids.rwsem held as a reader
707 708 709 710 711 712
 */
static void shm_add_rss_swap(struct shmid_kernel *shp,
	unsigned long *rss_add, unsigned long *swp_add)
{
	struct inode *inode;

Al Viro's avatar
Al Viro committed
713
	inode = file_inode(shp->shm_file);
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731

	if (is_file_hugepages(shp->shm_file)) {
		struct address_space *mapping = inode->i_mapping;
		struct hstate *h = hstate_file(shp->shm_file);
		*rss_add += pages_per_huge_page(h) * mapping->nrpages;
	} else {
#ifdef CONFIG_SHMEM
		struct shmem_inode_info *info = SHMEM_I(inode);
		spin_lock(&info->lock);
		*rss_add += inode->i_mapping->nrpages;
		*swp_add += info->swapped;
		spin_unlock(&info->lock);
#else
		*rss_add += inode->i_mapping->nrpages;
#endif
	}
}

Nadia Derbey's avatar
Nadia Derbey committed
732
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
733
 * Called with shm_ids.rwsem held as a reader
Nadia Derbey's avatar
Nadia Derbey committed
734
 */
Kirill Korotaev's avatar
Kirill Korotaev committed
735 736
static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss,
		unsigned long *swp)
Linus Torvalds's avatar
Linus Torvalds committed
737
{
Nadia Derbey's avatar
Nadia Derbey committed
738 739
	int next_id;
	int total, in_use;
Linus Torvalds's avatar
Linus Torvalds committed
740 741 742 743

	*rss = 0;
	*swp = 0;

Nadia Derbey's avatar
Nadia Derbey committed
744 745 746
	in_use = shm_ids(ns).in_use;

	for (total = 0, next_id = 0; total < in_use; next_id++) {
747
		struct kern_ipc_perm *ipc;
Linus Torvalds's avatar
Linus Torvalds committed
748 749
		struct shmid_kernel *shp;

750 751
		ipc = idr_find(&shm_ids(ns).ipcs_idr, next_id);
		if (ipc == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
752
			continue;
753
		shp = container_of(ipc, struct shmid_kernel, shm_perm);
Linus Torvalds's avatar
Linus Torvalds committed
754

755
		shm_add_rss_swap(shp, rss, swp);
Nadia Derbey's avatar
Nadia Derbey committed
756 757

		total++;
Linus Torvalds's avatar
Linus Torvalds committed
758 759 760
	}
}

761
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
762
 * This function handles some shmctl commands which require the rwsem
763
 * to be held in write mode.
Davidlohr Bueso's avatar
Davidlohr Bueso committed
764
 * NOTE: no locks must be held, the rwsem is taken inside this function.
765 766 767
 */
static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
		       struct shmid_ds __user *buf, int version)
Linus Torvalds's avatar
Linus Torvalds committed
768
{
769
	struct kern_ipc_perm *ipcp;
770
	struct shmid64_ds shmid64;
771 772 773 774
	struct shmid_kernel *shp;
	int err;

	if (cmd == IPC_SET) {
775
		if (copy_shmid_from_user(&shmid64, buf, version))
776 777 778
			return -EFAULT;
	}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
779
	down_write(&shm_ids(ns).rwsem);
780 781
	rcu_read_lock();

782 783
	ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd,
				      &shmid64.shm_perm, 0);
784 785 786 787
	if (IS_ERR(ipcp)) {
		err = PTR_ERR(ipcp);
		goto out_unlock1;
	}
788

789
	shp = container_of(ipcp, struct shmid_kernel, shm_perm);
790 791 792

	err = security_shm_shmctl(shp, cmd);
	if (err)
793
		goto out_unlock1;
794

795 796
	switch (cmd) {
	case IPC_RMID:
797
		ipc_lock_object(&shp->shm_perm);
798
		/* do_shm_rmid unlocks the ipc object and rcu */
799 800 801
		do_shm_rmid(ns, ipcp);
		goto out_up;
	case IPC_SET:
802
		ipc_lock_object(&shp->shm_perm);
803 804
		err = ipc_update_perm(&shmid64.shm_perm, ipcp);
		if (err)
805
			goto out_unlock0;
806 807 808 809
		shp->shm_ctim = get_seconds();
		break;
	default:
		err = -EINVAL;
810
		goto out_unlock1;
811
	}
812 813 814 815 816

out_unlock0:
	ipc_unlock_object(&shp->shm_perm);
out_unlock1:
	rcu_read_unlock();
817
out_up:
Davidlohr Bueso's avatar
Davidlohr Bueso committed
818
	up_write(&shm_ids(ns).rwsem);
819 820 821
	return err;
}

822 823
static int shmctl_nolock(struct ipc_namespace *ns, int shmid,
			 int cmd, int version, void __user *buf)
824
{
825
	int err;
Linus Torvalds's avatar
Linus Torvalds committed
826 827
	struct shmid_kernel *shp;

828 829 830 831 832
	/* preliminary security checks for *_INFO */
	if (cmd == IPC_INFO || cmd == SHM_INFO) {
		err = security_shm_shmctl(NULL, cmd);
		if (err)
			return err;
Linus Torvalds's avatar
Linus Torvalds committed
833 834
	}

835
	switch (cmd) {
Linus Torvalds's avatar
Linus Torvalds committed
836 837 838 839
	case IPC_INFO:
	{
		struct shminfo64 shminfo;

WANG Cong's avatar
WANG Cong committed
840
		memset(&shminfo, 0, sizeof(shminfo));
Kirill Korotaev's avatar
Kirill Korotaev committed
841 842 843
		shminfo.shmmni = shminfo.shmseg = ns->shm_ctlmni;
		shminfo.shmmax = ns->shm_ctlmax;
		shminfo.shmall = ns->shm_ctlall;
Linus Torvalds's avatar
Linus Torvalds committed
844 845 846 847

		shminfo.shmmin = SHMMIN;
		if(copy_shminfo_to_user (buf, &shminfo, version))
			return -EFAULT;
Nadia Derbey's avatar
Nadia Derbey committed
848

Davidlohr Bueso's avatar
Davidlohr Bueso committed
849
		down_read(&shm_ids(ns).rwsem);
Nadia Derbey's avatar
Nadia Derbey committed
850
		err = ipc_get_maxid(&shm_ids(ns));
Davidlohr Bueso's avatar
Davidlohr Bueso committed
851
		up_read(&shm_ids(ns).rwsem);
Nadia Derbey's avatar
Nadia Derbey committed
852

Linus Torvalds's avatar
Linus Torvalds committed
853 854 855 856 857 858 859 860
		if(err<0)
			err = 0;
		goto out;
	}
	case SHM_INFO:
	{
		struct shm_info shm_info;

WANG Cong's avatar
WANG Cong committed
861
		memset(&shm_info, 0, sizeof(shm_info));
Davidlohr Bueso's avatar
Davidlohr Bueso committed
862
		down_read(&shm_ids(ns).rwsem);
Kirill Korotaev's avatar
Kirill Korotaev committed
863 864 865
		shm_info.used_ids = shm_ids(ns).in_use;
		shm_get_stat (ns, &shm_info.shm_rss, &shm_info.shm_swp);
		shm_info.shm_tot = ns->shm_tot;
Linus Torvalds's avatar
Linus Torvalds committed
866 867
		shm_info.swap_attempts = 0;
		shm_info.swap_successes = 0;
Nadia Derbey's avatar
Nadia Derbey committed
868
		err = ipc_get_maxid(&shm_ids(ns));
Davidlohr Bueso's avatar
Davidlohr Bueso committed
869
		up_read(&shm_ids(ns).rwsem);
WANG Cong's avatar
WANG Cong committed
870
		if (copy_to_user(buf, &shm_info, sizeof(shm_info))) {
Linus Torvalds's avatar
Linus Torvalds committed
871 872 873 874 875 876 877 878 879 880 881 882
			err = -EFAULT;
			goto out;
		}

		err = err < 0 ? 0 : err;
		goto out;
	}
	case SHM_STAT:
	case IPC_STAT:
	{
		struct shmid64_ds tbuf;
		int result;
883

884
		rcu_read_lock();
885
		if (cmd == SHM_STAT) {
886
			shp = shm_obtain_object(ns, shmid);
887 888
			if (IS_ERR(shp)) {
				err = PTR_ERR(shp);
889
				goto out_unlock;
890
			}
Nadia Derbey's avatar
Nadia Derbey committed
891
			result = shp->shm_perm.id;
Linus Torvalds's avatar
Linus Torvalds committed
892
		} else {
893
			shp = shm_obtain_object_check(ns, shmid);
894 895
			if (IS_ERR(shp)) {
				err = PTR_ERR(shp);
896
				goto out_unlock;
897
			}
Linus Torvalds's avatar
Linus Torvalds committed
898 899
			result = 0;
		}
900

WANG Cong's avatar
WANG Cong committed
901
		err = -EACCES;
902
		if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
Linus Torvalds's avatar
Linus Torvalds committed
903
			goto out_unlock;
904

Linus Torvalds's avatar
Linus Torvalds committed
905 906 907
		err = security_shm_shmctl(shp, cmd);
		if (err)
			goto out_unlock;
908

909
		memset(&tbuf, 0, sizeof(tbuf));
Linus Torvalds's avatar
Linus Torvalds committed
910 911 912 913 914 915 916
		kernel_to_ipc64_perm(&shp->shm_perm, &tbuf.shm_perm);
		tbuf.shm_segsz	= shp->shm_segsz;
		tbuf.shm_atime	= shp->shm_atim;
		tbuf.shm_dtime	= shp->shm_dtim;
		tbuf.shm_ctime	= shp->shm_ctim;
		tbuf.shm_cpid	= shp->shm_cprid;
		tbuf.shm_lpid	= shp->shm_lprid;
917
		tbuf.shm_nattch	= shp->shm_nattch;
918 919 920
		rcu_read_unlock();

		if (copy_shmid_to_user(buf, &tbuf, version))
Linus Torvalds's avatar
Linus Torvalds committed
921 922 923 924 925
			err = -EFAULT;
		else
			err = result;
		goto out;
	}
926 927 928 929 930
	default:
		return -EINVAL;
	}

out_unlock:
931
	rcu_read_unlock();
932 933 934 935 936 937 938 939 940 941
out:
	return err;
}

SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
{
	struct shmid_kernel *shp;
	int err, version;
	struct ipc_namespace *ns;

942 943
	if (cmd < 0 || shmid < 0)
		return -EINVAL;
944 945 946 947 948 949 950 951 952 953

	version = ipc_parse_version(&cmd);
	ns = current->nsproxy->ipc_ns;

	switch (cmd) {
	case IPC_INFO:
	case SHM_INFO:
	case SHM_STAT:
	case IPC_STAT:
		return shmctl_nolock(ns, shmid, cmd, version, buf);
954 955 956
	case IPC_RMID:
	case IPC_SET:
		return shmctl_down(ns, shmid, cmd, buf, version);
Linus Torvalds's avatar
Linus Torvalds committed
957 958 959
	case SHM_LOCK:
	case SHM_UNLOCK:
	{
960
		struct file *shm_file;
961

962 963
		rcu_read_lock();
		shp = shm_obtain_object_check(ns, shmid);
964 965
		if (IS_ERR(shp)) {
			err = PTR_ERR(shp);
966
			goto out_unlock1;
Linus Torvalds's avatar
Linus Torvalds committed
967 968
		}

Al Viro's avatar
Al Viro committed
969
		audit_ipc_obj(&(shp->shm_perm));
970 971 972
		err = security_shm_shmctl(shp, cmd);
		if (err)
			goto out_unlock1;
Steve Grubb's avatar
Steve Grubb committed
973

974
		ipc_lock_object(&shp->shm_perm);
975
		if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {
976
			kuid_t euid = current_euid();
Linus Torvalds's avatar
Linus Torvalds committed
977
			err = -EPERM;
978 979
			if (!uid_eq(euid, shp->shm_perm.uid) &&
			    !uid_eq(euid, shp->shm_perm.cuid))
980
				goto out_unlock0;
Jiri Slaby's avatar
Jiri Slaby committed
981
			if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK))
982
				goto out_unlock0;
Linus Torvalds's avatar
Linus Torvalds committed
983 984
		}

985 986
		shm_file = shp->shm_file;
		if (is_file_hugepages(shm_file))
987
			goto out_unlock0;
988 989

		if (cmd == SHM_LOCK) {
990
			struct user_struct *user = current_user();
991 992 993 994
			err = shmem_lock(shm_file, 1, user);
			if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) {
				shp->shm_perm.mode |= SHM_LOCKED;
				shp->mlock_user = user;
Linus Torvalds's avatar
Linus Torvalds committed
995
			}
996
			goto out_unlock0;
Linus Torvalds's avatar
Linus Torvalds committed
997
		}
998 999 1000

		/* SHM_UNLOCK */
		if (!(shp->shm_perm.mode & SHM_LOCKED))
1001
			goto out_unlock0;
1002 1003 1004 1005
		shmem_lock(shm_file, 0, shp->mlock_user);
		shp->shm_perm.mode &= ~SHM_LOCKED;
		shp->mlock_user = NULL;
		get_file(shm_file);
1006 1007
		ipc_unlock_object(&shp->shm_perm);
		rcu_read_unlock();
1008
		shmem_unlock_mapping(shm_file->f_mapping);
1009

1010
		fput(shm_file);
1011
		return err;
1012
	}
Linus Torvalds's avatar
Linus Torvalds committed
1013
	default:
1014
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
1015 1016
	}

1017 1018 1019 1020
out_unlock0:
	ipc_unlock_object(&shp->shm_perm);
out_unlock1:
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
	return err;
}

/*
 * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists.
 *
 * NOTE! Despite the name, this is NOT a direct system call entrypoint. The
 * "raddr" thing points to kernel space, and there has to be a wrapper around
 * this.
 */
Will Deacon's avatar
Will Deacon committed
1031 1032
long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,
	      unsigned long shmlba)
Linus Torvalds's avatar
Linus Torvalds committed
1033 1034 1035 1036 1037 1038 1039 1040 1041
{
	struct shmid_kernel *shp;
	unsigned long addr;
	unsigned long size;
	struct file * file;
	int    err;
	unsigned long flags;
	unsigned long prot;
	int acc_mode;
Kirill Korotaev's avatar
Kirill Korotaev committed
1042
	struct ipc_namespace *ns;
1043 1044
	struct shm_file_data *sfd;
	struct path path;
1045
	fmode_t f_mode;
1046
	unsigned long populate = 0;
Linus Torvalds's avatar
Linus Torvalds committed
1047

1048 1049
	err = -EINVAL;
	if (shmid < 0)
Linus Torvalds's avatar
Linus Torvalds committed
1050
		goto out;
1051
	else if ((addr = (ulong)shmaddr)) {
Will Deacon's avatar
Will Deacon committed
1052
		if (addr & (shmlba - 1)) {
Linus Torvalds's avatar
Linus Torvalds committed
1053
			if (shmflg & SHM_RND)
Will Deacon's avatar
Will Deacon committed
1054
				addr &= ~(shmlba - 1);	   /* round down */
Linus Torvalds's avatar
Linus Torvalds committed
1055 1056 1057 1058
			else
#ifndef __ARCH_FORCE_SHMLBA
				if (addr & ~PAGE_MASK)
#endif
1059
					goto out;
Linus Torvalds's avatar
Linus Torvalds committed
1060 1061 1062 1063
		}
		flags = MAP_SHARED | MAP_FIXED;
	} else {
		if ((shmflg & SHM_REMAP))
1064
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
1065 1066 1067 1068 1069 1070 1071

		flags = MAP_SHARED;
	}

	if (shmflg & SHM_RDONLY) {
		prot = PROT_READ;
		acc_mode = S_IRUGO;
1072
		f_mode = FMODE_READ;
Linus Torvalds's avatar
Linus Torvalds committed
1073 1074 1075
	} else {
		prot = PROT_READ | PROT_WRITE;
		acc_mode = S_IRUGO | S_IWUGO;
1076
		f_mode = FMODE_READ | FMODE_WRITE;
Linus Torvalds's avatar
Linus Torvalds committed
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
	}
	if (shmflg & SHM_EXEC) {
		prot |= PROT_EXEC;
		acc_mode |= S_IXUGO;
	}

	/*
	 * We cannot rely on the fs check since SYSV IPC does have an
	 * additional creator id...
	 */
Kirill Korotaev's avatar
Kirill Korotaev committed
1087
	ns = current->nsproxy->ipc_ns;
1088 1089
	rcu_read_lock();
	shp = shm_obtain_object_check(ns, shmid);
1090 1091
	if (IS_ERR(shp)) {
		err = PTR_ERR(shp);
1092
		goto out_unlock;
1093
	}
1094 1095

	err = -EACCES;
1096
	if (ipcperms(ns, &shp->shm_perm, acc_mode))
1097
		goto out_unlock;
Linus Torvalds's avatar
Linus Torvalds committed
1098 1099

	err = security_shm_shmat(shp, shmaddr, shmflg);
1100 1101 1102
	if (err)
		goto out_unlock;

1103
	ipc_lock_object(&shp->shm_perm);
1104 1105
	path = shp->shm_file->f_path;
	path_get(&path);
Linus Torvalds's avatar
Linus Torvalds committed
1106
	shp->shm_nattch++;
1107
	size = i_size_read(path.dentry->d_inode);
1108 1109
	ipc_unlock_object(&shp->shm_perm);
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
1110

1111 1112
	err = -ENOMEM;
	sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);