Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2652ae6 authored by Manfred Spraul's avatar Manfred Spraul Committed by Linus Torvalds
Browse files

ipc: reorganize initialization of kern_ipc_perm.seq

ipc_addid() initializes kern_ipc_perm.seq after having called idr_alloc()
(within ipc_idr_alloc()).

Thus a parallel semop() or msgrcv() that uses ipc_obtain_object_check()
may see an uninitialized value.

The patch moves the initialization of kern_ipc_perm.seq before the calls
of idr_alloc().

Notes:
1) This patch has a user space visible side effect:
If /proc/sys/kernel/*_next_id is used (i.e.: checkpoint/restore) and
if semget()/msgget()/shmget() fails in the final step of adding the id
to the rhash tree, then .._next_id is cleared. Before the patch, is
remained unmodified.

There is no change of the behavior after a successful ..get() call: It
always clears .._next_id, there is no impact to non checkpoint/restore
code as that code does not use .._next_id.

2) The patch correctly documents that after a call to ipc_idr_alloc(),
the full tear-down sequence must be used. The callers of ipc_addid()
do not fullfill that, i.e. more bugfixes are required.

The patch is a squash of a patch from Dmitry and my own changes.

Link: http://lkml.kernel.org/r/20180712185241.4017-3-manfred@colorfullife.com


Reported-by: default avatar <syzbot+2827ef6b3385deb07eaf@syzkaller.appspotmail.com>
Signed-off-by: default avatarManfred Spraul <manfred@colorfullife.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Davidlohr Bueso <dbueso@suse.de>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: Michal Hocko <mhocko@suse.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 615c999c
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -464,7 +464,8 @@ Notes:
1) kernel doesn't guarantee, that new object will have desired id. So,
it's up to userspace, how to handle an object with "wrong" id.
2) Toggle with non-default value will be set back to -1 by kernel after
successful IPC object allocation.
successful IPC object allocation. If an IPC object allocation syscall
fails, it is undefined if the value remains unmodified or is reset to -1.

==============================================================

+48 −43
Original line number Diff line number Diff line
@@ -194,45 +194,53 @@ static struct kern_ipc_perm *ipc_findkey(struct ipc_ids *ids, key_t key)
	return NULL;
}

#ifdef CONFIG_CHECKPOINT_RESTORE
/*
 * Specify desired id for next allocated IPC object.
 * Insert new IPC object into idr tree, and set sequence number and id
 * in the correct order.
 * Especially:
 * - the sequence number must be set before inserting the object into the idr,
 *   because the sequence number is accessed without a lock.
 * - the id can/must be set after inserting the object into the idr.
 *   All accesses must be done after getting kern_ipc_perm.lock.
 *
 * The caller must own kern_ipc_perm.lock.of the new object.
 * On error, the function returns a (negative) error code.
 */
#define ipc_idr_alloc(ids, new)						\
	idr_alloc(&(ids)->ipcs_idr, (new),				\
		  (ids)->next_id < 0 ? 0 : ipcid_to_idx((ids)->next_id),\
		  0, GFP_NOWAIT)

static inline int ipc_buildid(int id, struct ipc_ids *ids,
			      struct kern_ipc_perm *new)
static inline int ipc_idr_alloc(struct ipc_ids *ids, struct kern_ipc_perm *new)
{
	if (ids->next_id < 0) { /* default, behave as !CHECKPOINT_RESTORE */
		new->seq = ids->seq++;
		if (ids->seq > IPCID_SEQ_MAX)
			ids->seq = 0;
	} else {
		new->seq = ipcid_to_seqx(ids->next_id);
		ids->next_id = -1;
	}
	int idx, next_id = -1;

	return SEQ_MULTIPLIER * new->seq + id;
}
#ifdef CONFIG_CHECKPOINT_RESTORE
	next_id = ids->next_id;
	ids->next_id = -1;
#endif

#else
#define ipc_idr_alloc(ids, new)					\
	idr_alloc(&(ids)->ipcs_idr, (new), 0, 0, GFP_NOWAIT)
	/*
	 * As soon as a new object is inserted into the idr,
	 * ipc_obtain_object_idr() or ipc_obtain_object_check() can find it,
	 * and the lockless preparations for ipc operations can start.
	 * This means especially: permission checks, audit calls, allocation
	 * of undo structures, ...
	 *
	 * Thus the object must be fully initialized, and if something fails,
	 * then the full tear-down sequence must be followed.
	 * (i.e.: set new->deleted, reduce refcount, call_rcu())
	 */

static inline int ipc_buildid(int id, struct ipc_ids *ids,
			      struct kern_ipc_perm *new)
{
	if (next_id < 0) { /* !CHECKPOINT_RESTORE or next_id is unset */
		new->seq = ids->seq++;
		if (ids->seq > IPCID_SEQ_MAX)
			ids->seq = 0;

	return SEQ_MULTIPLIER * new->seq + id;
		idx = idr_alloc(&ids->ipcs_idr, new, 0, 0, GFP_NOWAIT);
	} else {
		new->seq = ipcid_to_seqx(next_id);
		idx = idr_alloc(&ids->ipcs_idr, new, ipcid_to_idx(next_id),
				0, GFP_NOWAIT);
	}
	if (idx >= 0)
		new->id = SEQ_MULTIPLIER * new->seq + idx;
	return idx;
}

#endif /* CONFIG_CHECKPOINT_RESTORE */

/**
 * ipc_addid - add an ipc identifier
@@ -251,7 +259,7 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit)
{
	kuid_t euid;
	kgid_t egid;
	int id, err;
	int idx, err;

	if (limit > IPCMNI)
		limit = IPCMNI;
@@ -271,30 +279,27 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit)
	new->cuid = new->uid = euid;
	new->gid = new->cgid = egid;

	id = ipc_idr_alloc(ids, new);
	idx = ipc_idr_alloc(ids, new);
	idr_preload_end();

	if (id >= 0 && new->key != IPC_PRIVATE) {
	if (idx >= 0 && new->key != IPC_PRIVATE) {
		err = rhashtable_insert_fast(&ids->key_ht, &new->khtnode,
					     ipc_kht_params);
		if (err < 0) {
			idr_remove(&ids->ipcs_idr, id);
			id = err;
			idr_remove(&ids->ipcs_idr, idx);
			idx = err;
		}
	}
	if (id < 0) {
	if (idx < 0) {
		spin_unlock(&new->lock);
		rcu_read_unlock();
		return id;
		return idx;
	}

	ids->in_use++;
	if (id > ids->max_id)
		ids->max_id = id;

	new->id = ipc_buildid(id, ids, new);

	return id;
	if (idx > ids->max_id)
		ids->max_id = idx;
	return idx;
}

/**