shm.c 31.3 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
}

Nadia Derbey's avatar
Nadia Derbey committed
170
static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s)
Linus Torvalds's avatar
Linus Torvalds committed
171
{
Nadia Derbey's avatar
Nadia Derbey committed
172
	ipc_rmid(&shm_ids(ns), &s->shm_perm);
Linus Torvalds's avatar
Linus Torvalds committed
173 174 175
}


176 177
/* 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
178
{
179 180
	struct file *file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);
Linus Torvalds's avatar
Linus Torvalds committed
181 182
	struct shmid_kernel *shp;

183
	shp = shm_lock(sfd->ns, sfd->id);
184
	BUG_ON(IS_ERR(shp));
Linus Torvalds's avatar
Linus Torvalds committed
185
	shp->shm_atim = get_seconds();
186
	shp->shm_lprid = task_tgid_vnr(current);
Linus Torvalds's avatar
Linus Torvalds committed
187 188 189 190 191 192 193
	shp->shm_nattch++;
	shm_unlock(shp);
}

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

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
/*
 * 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
232
/*
233
 * remove the attach descriptor vma.
Linus Torvalds's avatar
Linus Torvalds committed
234 235 236 237
 * 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.
 */
238
static void shm_close(struct vm_area_struct *vma)
Linus Torvalds's avatar
Linus Torvalds committed
239
{
240 241
	struct file * file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);
Linus Torvalds's avatar
Linus Torvalds committed
242
	struct shmid_kernel *shp;
243
	struct ipc_namespace *ns = sfd->ns;
Kirill Korotaev's avatar
Kirill Korotaev committed
244

Davidlohr Bueso's avatar
Davidlohr Bueso committed
245
	down_write(&shm_ids(ns).rwsem);
Linus Torvalds's avatar
Linus Torvalds committed
246
	/* remove from the list of attaches of the shm segment */
Nadia Derbey's avatar
Nadia Derbey committed
247
	shp = shm_lock(ns, sfd->id);
248
	BUG_ON(IS_ERR(shp));
249
	shp->shm_lprid = task_tgid_vnr(current);
Linus Torvalds's avatar
Linus Torvalds committed
250 251
	shp->shm_dtim = get_seconds();
	shp->shm_nattch--;
252 253 254 255
	if (shm_may_destroy(ns, shp))
		shm_destroy(ns, shp);
	else
		shm_unlock(shp);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
256
	up_write(&shm_ids(ns).rwsem);
257 258
}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
259
/* Called with ns->shm_ids(ns).rwsem locked */
260 261 262
static int shm_try_destroy_current(int id, void *p, void *data)
{
	struct ipc_namespace *ns = data;
263 264
	struct kern_ipc_perm *ipcp = p;
	struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm);
265

266
	if (shp->shm_creator != current)
267 268 269 270 271 272 273 274 275 276 277 278 279
		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.
	 */
280
	if (!ns->shm_rmid_forced)
281 282
		return 0;

283 284
	if (shm_may_destroy(ns, shp)) {
		shm_lock_by_ptr(shp);
285
		shm_destroy(ns, shp);
286
	}
287 288 289
	return 0;
}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
290
/* Called with ns->shm_ids(ns).rwsem locked */
291 292 293
static int shm_try_destroy_orphaned(int id, void *p, void *data)
{
	struct ipc_namespace *ns = data;
294 295
	struct kern_ipc_perm *ipcp = p;
	struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm);
296 297 298 299

	/*
	 * We want to destroy segments without users and with already
	 * exit'ed originating process.
300
	 *
Davidlohr Bueso's avatar
Davidlohr Bueso committed
301
	 * As shp->* are changed under rwsem, it's safe to skip shp locking.
302
	 */
303
	if (shp->shm_creator != NULL)
304 305
		return 0;

306 307
	if (shm_may_destroy(ns, shp)) {
		shm_lock_by_ptr(shp);
Kirill Korotaev's avatar
Kirill Korotaev committed
308
		shm_destroy(ns, shp);
309
	}
310 311 312 313 314
	return 0;
}

void shm_destroy_orphaned(struct ipc_namespace *ns)
{
Davidlohr Bueso's avatar
Davidlohr Bueso committed
315
	down_write(&shm_ids(ns).rwsem);
Vasiliy Kulikov's avatar
Vasiliy Kulikov committed
316
	if (shm_ids(ns).in_use)
317
		idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_orphaned, ns);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
318
	up_write(&shm_ids(ns).rwsem);
319 320 321 322 323
}


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

Vasiliy Kulikov's avatar
Vasiliy Kulikov committed
326 327 328
	if (shm_ids(ns).in_use == 0)
		return;

329
	/* Destroy all already created segments, but not mapped yet */
Davidlohr Bueso's avatar
Davidlohr Bueso committed
330
	down_write(&shm_ids(ns).rwsem);
Vasiliy Kulikov's avatar
Vasiliy Kulikov committed
331
	if (shm_ids(ns).in_use)
332
		idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_current, ns);
Davidlohr Bueso's avatar
Davidlohr Bueso committed
333
	up_write(&shm_ids(ns).rwsem);
Linus Torvalds's avatar
Linus Torvalds committed
334 335
}

Nick Piggin's avatar
Nick Piggin committed
336
static int shm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
337 338 339 340
{
	struct file *file = vma->vm_file;
	struct shm_file_data *sfd = shm_file_data(file);

Nick Piggin's avatar
Nick Piggin committed
341
	return sfd->vm_ops->fault(vma, vmf);
342 343 344
}

#ifdef CONFIG_NUMA
345
static int shm_set_policy(struct vm_area_struct *vma, struct mempolicy *new)
346 347 348 349 350 351 352 353 354
{
	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;
}

355 356
static struct mempolicy *shm_get_policy(struct vm_area_struct *vma,
					unsigned long addr)
357 358 359 360 361 362 363
{
	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);
364
	else if (vma->vm_policy)
365
		pol = vma->vm_policy;
366

367 368 369 370
	return pol;
}
#endif

Linus Torvalds's avatar
Linus Torvalds committed
371 372
static int shm_mmap(struct file * file, struct vm_area_struct * vma)
{
373
	struct shm_file_data *sfd = shm_file_data(file);
374 375
	int ret;

376 377 378 379
	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
380
#ifdef CONFIG_MMU
381
	BUG_ON(!sfd->vm_ops->fault);
David Howells's avatar
David Howells committed
382
#endif
383 384
	vma->vm_ops = &shm_vm_ops;
	shm_open(vma);
385 386

	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
387 388
}

Kirill Korotaev's avatar
Kirill Korotaev committed
389 390
static int shm_release(struct inode *ino, struct file *file)
{
391
	struct shm_file_data *sfd = shm_file_data(file);
Kirill Korotaev's avatar
Kirill Korotaev committed
392

393 394 395
	put_ipc_ns(sfd->ns);
	shm_file_data(file) = NULL;
	kfree(sfd);
Kirill Korotaev's avatar
Kirill Korotaev committed
396 397 398
	return 0;
}

399
static int shm_fsync(struct file *file, loff_t start, loff_t end, int datasync)
400 401 402
{
	struct shm_file_data *sfd = shm_file_data(file);

403 404
	if (!sfd->file->f_op->fsync)
		return -EINVAL;
405
	return sfd->file->f_op->fsync(sfd->file, start, end, datasync);
406 407
}

408 409 410 411 412 413 414 415 416 417
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);
}

418 419 420 421 422
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);
423 424
	return sfd->file->f_op->get_unmapped_area(sfd->file, addr, len,
						pgoff, flags);
425 426
}

427
static const struct file_operations shm_file_operations = {
Kirill Korotaev's avatar
Kirill Korotaev committed
428
	.mmap		= shm_mmap,
429
	.fsync		= shm_fsync,
Kirill Korotaev's avatar
Kirill Korotaev committed
430
	.release	= shm_release,
David Howells's avatar
David Howells committed
431 432 433
#ifndef CONFIG_MMU
	.get_unmapped_area	= shm_get_unmapped_area,
#endif
434
	.llseek		= noop_llseek,
435
	.fallocate	= shm_fallocate,
436 437 438 439 440 441
};

static const struct file_operations shm_file_operations_huge = {
	.mmap		= shm_mmap,
	.fsync		= shm_fsync,
	.release	= shm_release,
442
	.get_unmapped_area	= shm_get_unmapped_area,
443
	.llseek		= noop_llseek,
444
	.fallocate	= shm_fallocate,
Linus Torvalds's avatar
Linus Torvalds committed
445 446
};

447 448 449 450 451
int is_file_shm_hugepages(struct file *file)
{
	return file->f_op == &shm_file_operations_huge;
}

452
static const struct vm_operations_struct shm_vm_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
453 454
	.open	= shm_open,	/* callback for a new vm-area open */
	.close	= shm_close,	/* callback for when the vm-area is released */
455
	.fault	= shm_fault,
456 457 458
#if defined(CONFIG_NUMA)
	.set_policy = shm_set_policy,
	.get_policy = shm_get_policy,
Linus Torvalds's avatar
Linus Torvalds committed
459 460 461
#endif
};

Nadia Derbey's avatar
Nadia Derbey committed
462 463 464 465 466
/**
 * 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
467
 * Called with shm_ids.rwsem held as a writer.
Nadia Derbey's avatar
Nadia Derbey committed
468 469
 */

Nadia Derbey's avatar
Nadia Derbey committed
470
static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
Linus Torvalds's avatar
Linus Torvalds committed
471
{
Nadia Derbey's avatar
Nadia Derbey committed
472 473 474
	key_t key = params->key;
	int shmflg = params->flg;
	size_t size = params->u.size;
Linus Torvalds's avatar
Linus Torvalds committed
475 476
	int error;
	struct shmid_kernel *shp;
477
	size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
Linus Torvalds's avatar
Linus Torvalds committed
478 479 480
	struct file * file;
	char name[13];
	int id;
481
	vm_flags_t acctflag = 0;
Linus Torvalds's avatar
Linus Torvalds committed
482

Kirill Korotaev's avatar
Kirill Korotaev committed
483
	if (size < SHMMIN || size > ns->shm_ctlmax)
Linus Torvalds's avatar
Linus Torvalds committed
484 485
		return -EINVAL;

486
	if (ns->shm_tot + numpages > ns->shm_ctlall)
Linus Torvalds's avatar
Linus Torvalds committed
487 488 489 490 491 492 493
		return -ENOSPC;

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

	shp->shm_perm.key = key;
Andrew Morton's avatar
Andrew Morton committed
494
	shp->shm_perm.mode = (shmflg & S_IRWXUGO);
Linus Torvalds's avatar
Linus Torvalds committed
495 496 497 498 499 500 501 502 503
	shp->mlock_user = NULL;

	shp->shm_perm.security = NULL;
	error = security_shm_alloc(shp);
	if (error) {
		ipc_rcu_putref(shp);
		return error;
	}

504
	sprintf (name, "SYSV%08x", key);
Linus Torvalds's avatar
Linus Torvalds committed
505
	if (shmflg & SHM_HUGETLB) {
506
		struct hstate *hs;
507 508
		size_t hugesize;

509
		hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
510 511 512 513 514
		if (!hs) {
			error = -EINVAL;
			goto no_file;
		}
		hugesize = ALIGN(size, huge_page_size(hs));
515

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

536
	id = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni);
537 538
	if (id < 0) {
		error = id;
Linus Torvalds's avatar
Linus Torvalds committed
539
		goto no_id;
540
	}
Linus Torvalds's avatar
Linus Torvalds committed
541

542
	shp->shm_cprid = task_tgid_vnr(current);
Linus Torvalds's avatar
Linus Torvalds committed
543 544 545 546 547 548
	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;
549
	shp->shm_creator = current;
550

551 552 553 554
	/*
	 * 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
555
	file_inode(file)->i_ino = shp->shm_perm.id;
556

Kirill Korotaev's avatar
Kirill Korotaev committed
557
	ns->shm_tot += numpages;
Nadia Derbey's avatar
Nadia Derbey committed
558
	error = shp->shm_perm.id;
559

560
	ipc_unlock_object(&shp->shm_perm);
561
	rcu_read_unlock();
Nadia Derbey's avatar
Nadia Derbey committed
562
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
563 564

no_id:
565
	if (is_file_hugepages(file) && shp->mlock_user)
566
		user_shm_unlock(size, shp->mlock_user);
Linus Torvalds's avatar
Linus Torvalds committed
567 568 569 570 571 572 573
	fput(file);
no_file:
	security_shm_free(shp);
	ipc_rcu_putref(shp);
	return error;
}

Nadia Derbey's avatar
Nadia Derbey committed
574
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
575
 * Called with shm_ids.rwsem and ipcp locked.
Nadia Derbey's avatar
Nadia Derbey committed
576
 */
Nadia Derbey's avatar
Nadia Derbey committed
577
static inline int shm_security(struct kern_ipc_perm *ipcp, int shmflg)
Nadia Derbey's avatar
Nadia Derbey committed
578
{
Nadia Derbey's avatar
Nadia Derbey committed
579 580 581 582
	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
583 584
}

Nadia Derbey's avatar
Nadia Derbey committed
585
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
586
 * Called with shm_ids.rwsem and ipcp locked.
Nadia Derbey's avatar
Nadia Derbey committed
587
 */
Nadia Derbey's avatar
Nadia Derbey committed
588 589
static inline int shm_more_checks(struct kern_ipc_perm *ipcp,
				struct ipc_params *params)
Nadia Derbey's avatar
Nadia Derbey committed
590
{
Nadia Derbey's avatar
Nadia Derbey committed
591 592 593 594
	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
595 596 597 598 599
		return -EINVAL;

	return 0;
}

600
SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg)
Linus Torvalds's avatar
Linus Torvalds committed
601
{
Kirill Korotaev's avatar
Kirill Korotaev committed
602
	struct ipc_namespace *ns;
Nadia Derbey's avatar
Nadia Derbey committed
603 604
	struct ipc_ops shm_ops;
	struct ipc_params shm_params;
Kirill Korotaev's avatar
Kirill Korotaev committed
605 606

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

Nadia Derbey's avatar
Nadia Derbey committed
608 609 610
	shm_ops.getnew = newseg;
	shm_ops.associate = shm_security;
	shm_ops.more_checks = shm_more_checks;
Nadia Derbey's avatar
Nadia Derbey committed
611

Nadia Derbey's avatar
Nadia Derbey committed
612 613 614
	shm_params.key = key;
	shm_params.flg = shmflg;
	shm_params.u.size = size;
Linus Torvalds's avatar
Linus Torvalds committed
615

Nadia Derbey's avatar
Nadia Derbey committed
616
	return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params);
Linus Torvalds's avatar
Linus Torvalds committed
617 618 619 620 621 622 623 624 625 626 627
}

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;

628
		memset(&out, 0, sizeof(out));
Linus Torvalds's avatar
Linus Torvalds committed
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
		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;
	}
}

645 646
static inline unsigned long
copy_shmid_from_user(struct shmid64_ds *out, void __user *buf, int version)
Linus Torvalds's avatar
Linus Torvalds committed
647 648 649
{
	switch(version) {
	case IPC_64:
650
		if (copy_from_user(out, buf, sizeof(*out)))
Linus Torvalds's avatar
Linus Torvalds committed
651 652 653 654 655 656 657 658 659
			return -EFAULT;
		return 0;
	case IPC_OLD:
	    {
		struct shmid_ds tbuf_old;

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

660 661 662
		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
663 664 665 666 667 668 669 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

		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;
	}
}

697 698
/*
 * Calculate and add used RSS and swap pages of a shm.
Davidlohr Bueso's avatar
Davidlohr Bueso committed
699
 * Called with shm_ids.rwsem held as a reader
700 701 702 703 704 705
 */
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
706
	inode = file_inode(shp->shm_file);
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724

	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
725
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
726
 * Called with shm_ids.rwsem held as a reader
Nadia Derbey's avatar
Nadia Derbey committed
727
 */
Kirill Korotaev's avatar
Kirill Korotaev committed
728 729
static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss,
		unsigned long *swp)
Linus Torvalds's avatar
Linus Torvalds committed
730
{
Nadia Derbey's avatar
Nadia Derbey committed
731 732
	int next_id;
	int total, in_use;
Linus Torvalds's avatar
Linus Torvalds committed
733 734 735 736

	*rss = 0;
	*swp = 0;

Nadia Derbey's avatar
Nadia Derbey committed
737 738 739
	in_use = shm_ids(ns).in_use;

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

743 744
		ipc = idr_find(&shm_ids(ns).ipcs_idr, next_id);
		if (ipc == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
745
			continue;
746
		shp = container_of(ipc, struct shmid_kernel, shm_perm);
Linus Torvalds's avatar
Linus Torvalds committed
747

748
		shm_add_rss_swap(shp, rss, swp);
Nadia Derbey's avatar
Nadia Derbey committed
749 750

		total++;
Linus Torvalds's avatar
Linus Torvalds committed
751 752 753
	}
}

754
/*
Davidlohr Bueso's avatar
Davidlohr Bueso committed
755
 * This function handles some shmctl commands which require the rwsem
756
 * to be held in write mode.
Davidlohr Bueso's avatar
Davidlohr Bueso committed
757
 * NOTE: no locks must be held, the rwsem is taken inside this function.
758 759 760
 */
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
761
{
762
	struct kern_ipc_perm *ipcp;
763
	struct shmid64_ds shmid64;
764 765 766 767
	struct shmid_kernel *shp;
	int err;

	if (cmd == IPC_SET) {
768
		if (copy_shmid_from_user(&shmid64, buf, version))
769 770 771
			return -EFAULT;
	}

Davidlohr Bueso's avatar
Davidlohr Bueso committed
772
	down_write(&shm_ids(ns).rwsem);
773 774
	rcu_read_lock();

775 776
	ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd,
				      &shmid64.shm_perm, 0);
777 778 779 780
	if (IS_ERR(ipcp)) {
		err = PTR_ERR(ipcp);
		goto out_unlock1;
	}
781

782
	shp = container_of(ipcp, struct shmid_kernel, shm_perm);
783 784 785

	err = security_shm_shmctl(shp, cmd);
	if (err)
786
		goto out_unlock1;
787

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

out_unlock0:
	ipc_unlock_object(&shp->shm_perm);
out_unlock1:
	rcu_read_unlock();
810
out_up:
Davidlohr Bueso's avatar
Davidlohr Bueso committed
811
	up_write(&shm_ids(ns).rwsem);
812 813 814
	return err;
}

815 816
static int shmctl_nolock(struct ipc_namespace *ns, int shmid,
			 int cmd, int version, void __user *buf)
817
{
818
	int err;
Linus Torvalds's avatar
Linus Torvalds committed
819 820
	struct shmid_kernel *shp;

821 822 823 824 825
	/* 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
826 827
	}

828
	switch (cmd) {
Linus Torvalds's avatar
Linus Torvalds committed
829 830 831 832
	case IPC_INFO:
	{
		struct shminfo64 shminfo;

WANG Cong's avatar
WANG Cong committed
833
		memset(&shminfo, 0, sizeof(shminfo));
Kirill Korotaev's avatar
Kirill Korotaev committed
834 835 836
		shminfo.shmmni = shminfo.shmseg = ns->shm_ctlmni;
		shminfo.shmmax = ns->shm_ctlmax;
		shminfo.shmall = ns->shm_ctlall;
Linus Torvalds's avatar
Linus Torvalds committed
837 838 839 840

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

Davidlohr Bueso's avatar
Davidlohr Bueso committed
842
		down_read(&shm_ids(ns).rwsem);
Nadia Derbey's avatar
Nadia Derbey committed
843
		err = ipc_get_maxid(&shm_ids(ns));
Davidlohr Bueso's avatar
Davidlohr Bueso committed
844
		up_read(&shm_ids(ns).rwsem);
Nadia Derbey's avatar
Nadia Derbey committed
845

Linus Torvalds's avatar
Linus Torvalds committed
846 847 848 849 850 851 852 853
		if(err<0)
			err = 0;
		goto out;
	}
	case SHM_INFO:
	{
		struct shm_info shm_info;

WANG Cong's avatar
WANG Cong committed
854
		memset(&shm_info, 0, sizeof(shm_info));
Davidlohr Bueso's avatar
Davidlohr Bueso committed
855
		down_read(&shm_ids(ns).rwsem);
Kirill Korotaev's avatar
Kirill Korotaev committed
856 857 858
		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
859 860
		shm_info.swap_attempts = 0;
		shm_info.swap_successes = 0;
Nadia Derbey's avatar
Nadia Derbey committed
861
		err = ipc_get_maxid(&shm_ids(ns));
Davidlohr Bueso's avatar
Davidlohr Bueso committed
862
		up_read(&shm_ids(ns).rwsem);
WANG Cong's avatar
WANG Cong committed
863
		if (copy_to_user(buf, &shm_info, sizeof(shm_info))) {
Linus Torvalds's avatar
Linus Torvalds committed
864 865 866 867 868 869 870 871 872 873 874 875
			err = -EFAULT;
			goto out;
		}

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

877
		rcu_read_lock();
878
		if (cmd == SHM_STAT) {
879
			shp = shm_obtain_object(ns, shmid);
880 881
			if (IS_ERR(shp)) {
				err = PTR_ERR(shp);
882
				goto out_unlock;
883
			}
Nadia Derbey's avatar
Nadia Derbey committed
884
			result = shp->shm_perm.id;
Linus Torvalds's avatar
Linus Torvalds committed
885
		} else {
886
			shp = shm_obtain_object_check(ns, shmid);
887 888
			if (IS_ERR(shp)) {
				err = PTR_ERR(shp);
889
				goto out_unlock;
890
			}
Linus Torvalds's avatar
Linus Torvalds committed
891 892
			result = 0;
		}
893

WANG Cong's avatar
WANG Cong committed
894
		err = -EACCES;
895
		if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
Linus Torvalds's avatar
Linus Torvalds committed
896
			goto out_unlock;
897

Linus Torvalds's avatar
Linus Torvalds committed
898 899 900
		err = security_shm_shmctl(shp, cmd);
		if (err)
			goto out_unlock;
901

902
		memset(&tbuf, 0, sizeof(tbuf));
Linus Torvalds's avatar
Linus Torvalds committed
903 904 905 906 907 908 909
		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;
910
		tbuf.shm_nattch	= shp->shm_nattch;
911 912 913
		rcu_read_unlock();

		if (copy_shmid_to_user(buf, &tbuf, version))
Linus Torvalds's avatar
Linus Torvalds committed
914 915 916 917 918
			err = -EFAULT;
		else
			err = result;
		goto out;
	}
919 920 921 922 923
	default:
		return -EINVAL;
	}

out_unlock:
924
	rcu_read_unlock();
925 926 927 928 929 930 931 932 933 934
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;

935 936
	if (cmd < 0 || shmid < 0)
		return -EINVAL;
937 938 939 940 941 942 943 944 945 946

	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);
947 948 949
	case IPC_RMID:
	case IPC_SET:
		return shmctl_down(ns, shmid, cmd, buf, version);
Linus Torvalds's avatar
Linus Torvalds committed
950 951 952
	case SHM_LOCK:
	case SHM_UNLOCK:
	{
953
		struct file *shm_file;
954

955 956
		rcu_read_lock();
		shp = shm_obtain_object_check(ns, shmid);
957 958
		if (IS_ERR(shp)) {
			err = PTR_ERR(shp);
959
			goto out_unlock1;
Linus Torvalds's avatar
Linus Torvalds committed
960 961
		}

Al Viro's avatar
Al Viro committed
962
		audit_ipc_obj(&(shp->shm_perm));
963 964 965
		err = security_shm_shmctl(shp, cmd);
		if (err)
			goto out_unlock1;
Steve Grubb's avatar
Steve Grubb committed
966

967
		ipc_lock_object(&shp->shm_perm);
968
		if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {
969
			kuid_t euid = current_euid();
Linus Torvalds's avatar
Linus Torvalds committed
970
			err = -EPERM;
971 972
			if (!uid_eq(euid, shp->shm_perm.uid) &&
			    !uid_eq(euid, shp->shm_perm.cuid))
973
				goto out_unlock0;
Jiri Slaby's avatar
Jiri Slaby committed
974
			if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK))
975
				goto out_unlock0;
Linus Torvalds's avatar
Linus Torvalds committed
976 977
		}

978 979
		shm_file = shp->shm_file;
		if (is_file_hugepages(shm_file))
980
			goto out_unlock0;
981 982

		if (cmd == SHM_LOCK) {
983
			struct user_struct *user = current_user();
984 985 986 987
			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
988
			}
989
			goto out_unlock0;
Linus Torvalds's avatar
Linus Torvalds committed
990
		}
991 992 993

		/* SHM_UNLOCK */
		if (!(shp->shm_perm.mode & SHM_LOCKED))
994
			goto out_unlock0;
995 996 997 998
		shmem_lock(shm_file, 0, shp->mlock_user);
		shp->shm_perm.mode &= ~SHM_LOCKED;
		shp->mlock_user = NULL;
		get_file(shm_file);
999 1000
		ipc_unlock_object(&shp->shm_perm);
		rcu_read_unlock();
1001
		shmem_unlock_mapping(shm_file->f_mapping);
1002

1003
		fput(shm_file);
1004
		return err;
1005
	}
Linus Torvalds's avatar
Linus Torvalds committed
1006
	default:
1007
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
1008 1009
	}

1010 1011 1012 1013
out_unlock0:
	ipc_unlock_object(&shp->shm_perm);
out_unlock1:
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
	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
1024 1025
long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,
	      unsigned long shmlba)
Linus Torvalds's avatar
Linus Torvalds committed
1026 1027 1028 1029 1030 1031 1032 1033 1034
{
	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
1035
	struct ipc_namespace *ns;
1036 1037
	struct shm_file_data *sfd;
	struct path path;
1038
	fmode_t f_mode;
1039
	unsigned long populate = 0;
Linus Torvalds's avatar
Linus Torvalds committed
1040

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

		flags = MAP_SHARED;
	}

	if (shmflg & SHM_RDONLY) {
		prot = PROT_READ;
		acc_mode = S_IRUGO;
1065
		f_mode = FMODE_READ;
Linus Torvalds's avatar
Linus Torvalds committed
1066 1067 1068
	} else {
		prot = PROT_READ | PROT_WRITE;
		acc_mode = S_IRUGO | S_IWUGO;
1069
		f_mode = FMODE_READ | FMODE_WRITE;
Linus Torvalds's avatar
Linus Torvalds committed
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
	}
	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
1080
	ns = current->nsproxy->ipc_ns;
1081 1082
	rcu_read_lock();
	shp = shm_obtain_object_check(ns, shmid);
1083 1084
	if (IS_ERR(shp)) {
		err = PTR_ERR(shp);
1085
		goto out_unlock;
1086
	}
1087 1088

	err = -EACCES;
1089
	if (ipcperms(ns, &shp->shm_perm, acc_mode))
1090
		goto out_unlock;
Linus Torvalds's avatar
Linus Torvalds committed
1091 1092

	err = security_shm_shmat(shp, shmaddr, shmflg);
1093 1094 1095
	if (err)
		goto out_unlock;

1096
	ipc_lock_object(&shp->shm_perm);
1097 1098
	path = shp->shm_file->f_path;
	path_get(&path);
Linus Torvalds's avatar
Linus Torvalds committed
1099
	shp->shm_nattch++;
1100
	size = i_size_read(path.dentry->d_inode);
1101 1102
	ipc_unlock_object(&shp->shm_perm);
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
1103

1104 1105
	err = -ENOMEM;
	sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);
1106 1107 1108 1109
	if (!sfd) {
		path_put(&path);
		goto out_nattch;
	}
1110

1111 1112
	file = alloc_file(&path, f_mode,
			  is_file_hugepages(shp->shm_file) ?
1113 1114
				&shm_file_operations_huge :
				&shm_file_operations);
1115
	err = PTR_ERR(file);