linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC] ipc: refcounting / use after free?
@ 2018-07-04 11:53 Manfred Spraul
  2018-07-04 14:45 ` Paul E. McKenney
  0 siblings, 1 reply; 2+ messages in thread
From: Manfred Spraul @ 2018-07-04 11:53 UTC (permalink / raw)
  To: LKML, Davidlohr Bueso, paulmck
  Cc: 1vier1, Andrew Morton, Kees Cook, Manfred Spraul

The ipc code uses the equivalent of

	rcu_read_lock();
	kfree_rcu(a, rcu);
	if (a->deleted) {
		rcu_read_unlock();
		return FAILURE;
	}
	<...>

Is this safe, or is dereferencing "a" after having called call_rcu()
a use-after-free?

According to rcupdate.h, the kfree is only deferred until the
other CPUs exit their critical sections:

include/linux/rcupdate.h:
> * Similarly, if call_rcu() is invoked
> * on one CPU while other CPUs are within RCU read-side critical
> * sections, invocation of the corresponding RCU callback is deferred
> * until after the all the other CPUs exit their critical sections.

<The patch is for review, not fully tested>
---
 ipc/msg.c  | 11 ++++++++---
 ipc/sem.c  | 42 ++++++++++++++++++++++++++++++------------
 ipc/util.c | 35 ++++++++++++++++++++++++++++++++---
 ipc/util.h | 18 ++++++++++++++++--
 4 files changed, 86 insertions(+), 20 deletions(-)

diff --git a/ipc/msg.c b/ipc/msg.c
index 3b6545302598..724000c15296 100644
--- a/ipc/msg.c
+++ b/ipc/msg.c
@@ -805,7 +805,7 @@ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
 	msq = msq_obtain_object_check(ns, msqid);
 	if (IS_ERR(msq)) {
 		err = PTR_ERR(msq);
-		goto out_unlock1;
+		goto out_unlock2;
 	}
 
 	ipc_lock_object(&msq->q_perm);
@@ -851,8 +851,12 @@ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
 		rcu_read_lock();
 		ipc_lock_object(&msq->q_perm);
 
-		ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
 		/* raced with RMID? */
+		if (!__ipc_rcu_putref(&msq->q_perm)) {
+			ipc_unlock_object(&msq->q_perm);
+			call_rcu(&msq->q_perm.rcu, msg_rcu_free);
+			goto out_unlock1;
+		}
 		if (!ipc_valid_object(&msq->q_perm)) {
 			err = -EIDRM;
 			goto out_unlock0;
@@ -883,8 +887,9 @@ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
 
 out_unlock0:
 	ipc_unlock_object(&msq->q_perm);
-	wake_up_q(&wake_q);
 out_unlock1:
+	wake_up_q(&wake_q);
+out_unlock2:
 	rcu_read_unlock();
 	if (msg != NULL)
 		free_msg(msg);
diff --git a/ipc/sem.c b/ipc/sem.c
index 5af1943ad782..c269fae05b24 100644
--- a/ipc/sem.c
+++ b/ipc/sem.c
@@ -475,10 +475,16 @@ static inline struct sem_array *sem_obtain_object_check(struct ipc_namespace *ns
 	return container_of(ipcp, struct sem_array, sem_perm);
 }
 
-static inline void sem_lock_and_putref(struct sem_array *sma)
+static int __must_check sem_lock_and_putref(struct sem_array *sma)
 {
 	sem_lock(sma, NULL, -1);
-	ipc_rcu_putref(&sma->sem_perm, sem_rcu_free);
+
+	if (!__ipc_rcu_putref(&sma->sem_perm)) {
+		sem_unlock(sma, -1);
+		call_rcu(&sma->sem_perm.rcu, sem_rcu_free);
+		return 0;
+	}
+	return 1;
 }
 
 static inline void sem_rmid(struct ipc_namespace *ns, struct sem_array *s)
@@ -1434,7 +1440,10 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
 			}
 
 			rcu_read_lock();
-			sem_lock_and_putref(sma);
+			if (!sem_lock_and_putref(sma)) {
+				goto out_rcu_wakeup;
+			}
+
 			if (!ipc_valid_object(&sma->sem_perm)) {
 				err = -EIDRM;
 				goto out_unlock;
@@ -1483,7 +1492,11 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
 			}
 		}
 		rcu_read_lock();
-		sem_lock_and_putref(sma);
+		if (!sem_lock_and_putref(sma)) {
+			err = -EIDRM;
+			goto out_rcu_wakeup;
+		}
+
 		if (!ipc_valid_object(&sma->sem_perm)) {
 			err = -EIDRM;
 			goto out_unlock;
@@ -1898,14 +1911,12 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid)
 
 	/* step 3: Acquire the lock on semaphore array */
 	rcu_read_lock();
-	sem_lock_and_putref(sma);
-	if (!ipc_valid_object(&sma->sem_perm)) {
-		sem_unlock(sma, -1);
-		rcu_read_unlock();
-		kfree(new);
-		un = ERR_PTR(-EIDRM);
-		goto out;
-	}
+	if (!sem_lock_and_putref(sma))
+		goto out_EIDRM_free;
+
+	if (!ipc_valid_object(&sma->sem_perm))
+		goto out_EIDRM_unlock;
+
 	spin_lock(&ulp->lock);
 
 	/*
@@ -1931,6 +1942,13 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid)
 	sem_unlock(sma, -1);
 out:
 	return un;
+
+out_EIDRM_unlock:
+	sem_unlock(sma, -1);
+out_EIDRM_free:
+	rcu_read_unlock();
+	kfree(new);
+	return ERR_PTR(-EIDRM);
 }
 
 static long do_semtimedop(int semid, struct sembuf __user *tsops,
diff --git a/ipc/util.c b/ipc/util.c
index 4e81182fa0ac..ba7f96fc8a61 100644
--- a/ipc/util.c
+++ b/ipc/util.c
@@ -462,18 +462,47 @@ void ipc_set_key_private(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
 	ipcp->key = IPC_PRIVATE;
 }
 
-int ipc_rcu_getref(struct kern_ipc_perm *ptr)
+int __must_check ipc_rcu_getref(struct kern_ipc_perm *ptr)
 {
 	return refcount_inc_not_zero(&ptr->refcount);
 }
 
-void ipc_rcu_putref(struct kern_ipc_perm *ptr,
+/**
+ * __ipc_rcu_putref - reduce reference count of an ipc object.
+ * @ptr: ipc object
+ *
+ * The function reduces the reference count of an ipc object by 1.
+ * If the reference count drops to 0, then the function returns 0.
+ * If this happens, then the caller is responsible for triggering
+ * call_rcu() to free the ipc object.
+ */
+int __must_check __ipc_rcu_putref(struct kern_ipc_perm *ptr)
+{
+	if (!refcount_dec_and_test(&ptr->refcount))
+		return 1;
+	return 0;
+}
+
+/**
+ * ipc_rcu_putref - reduce reference count of an ipc object.
+ * @ptr: ipc object
+ * @func: cleanup function to call when the reference is reduced to 0.
+ *
+ * The function reduces the reference count of an ipc object by 1.
+ * If the count drops to 0, then a call_rcu is triggered to free the ipc
+ * object.
+ *
+ * If the reference count drops to 0, then the function returns 0.
+ * If this happens, then the caller must not dereference ptr.
+ */
+int ipc_rcu_putref(struct kern_ipc_perm *ptr,
 			void (*func)(struct rcu_head *head))
 {
 	if (!refcount_dec_and_test(&ptr->refcount))
-		return;
+		return 1;
 
 	call_rcu(&ptr->rcu, func);
+	return 0;
 }
 
 /**
diff --git a/ipc/util.h b/ipc/util.h
index 0aba3230d007..fbd55aeee933 100644
--- a/ipc/util.h
+++ b/ipc/util.h
@@ -134,12 +134,26 @@ static inline int ipc_get_maxid(struct ipc_ids *ids)
  * Objects are reference counted, they start with reference count 1.
  * getref increases the refcount, the putref call that reduces the recount
  * to 0 schedules the rcu destruction. Caller must guarantee locking.
+ * As the reference count is reduced without holding any lock, this means
+ * that the caller must use ipc_valid_object() after acquiring ipc_lock_xx().
  *
  * refcount is initialized by ipc_addid(), before that point call_rcu()
  * must be used.
+ *
+ * Most callers must check the return codes:
+ * - ipc_rcu_getref() fails if the reference count is already 0.
+ * - ipc_rcu_putref() calls call_rcu() if the reference count drops to 0,
+ *	dereferencing the ipc object if the function returns 0 is a
+ *	use-after-free.
+ * - with __ipc_rcu_putref(), the caller is responible for call_rcu().
+ *	This function is intended to be used if dereferencing must be done,
+ *	e.g. to drop a lock.
+ * ipc_rcu_putref does not use __must_check, because error paths or the
+ * IPC_RMID codepaths can safely ignore the return code.
  */
-int ipc_rcu_getref(struct kern_ipc_perm *ptr);
-void ipc_rcu_putref(struct kern_ipc_perm *ptr,
+int __must_check ipc_rcu_getref(struct kern_ipc_perm *ptr);
+int __must_check __ipc_rcu_putref(struct kern_ipc_perm *ptr);
+int ipc_rcu_putref(struct kern_ipc_perm *ptr,
 			void (*func)(struct rcu_head *head));
 
 struct kern_ipc_perm *ipc_lock(struct ipc_ids *, int);
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [RFC] ipc: refcounting / use after free?
  2018-07-04 11:53 [RFC] ipc: refcounting / use after free? Manfred Spraul
@ 2018-07-04 14:45 ` Paul E. McKenney
  0 siblings, 0 replies; 2+ messages in thread
From: Paul E. McKenney @ 2018-07-04 14:45 UTC (permalink / raw)
  To: Manfred Spraul; +Cc: LKML, Davidlohr Bueso, 1vier1, Andrew Morton, Kees Cook

On Wed, Jul 04, 2018 at 01:53:05PM +0200, Manfred Spraul wrote:
> The ipc code uses the equivalent of
> 
> 	rcu_read_lock();
> 	kfree_rcu(a, rcu);
> 	if (a->deleted) {
> 		rcu_read_unlock();
> 		return FAILURE;
> 	}
> 	<...>
> 
> Is this safe, or is dereferencing "a" after having called call_rcu()
> a use-after-free?

This is safe because it is being done before the rcu_read_unlock().

> According to rcupdate.h, the kfree is only deferred until the
> other CPUs exit their critical sections:
> 
> include/linux/rcupdate.h:
> > * Similarly, if call_rcu() is invoked
> > * on one CPU while other CPUs are within RCU read-side critical
> > * sections, invocation of the corresponding RCU callback is deferred
> > * until after the all the other CPUs exit their critical sections.

This is true, but so are the words that appear earlier in that comment:

 * The callback function will be invoked some time after a full grace
 * period elapses, in other words after all pre-existing RCU read-side
 * critical sections have completed.

In the above case, the kfree_rcu() is executed within a pre-existing
RCU read-side critical section, so the memory cannot be freed until
the rcu_read_lock() is reached.

							Thanx, Paul

> <The patch is for review, not fully tested>
> ---
>  ipc/msg.c  | 11 ++++++++---
>  ipc/sem.c  | 42 ++++++++++++++++++++++++++++++------------
>  ipc/util.c | 35 ++++++++++++++++++++++++++++++++---
>  ipc/util.h | 18 ++++++++++++++++--
>  4 files changed, 86 insertions(+), 20 deletions(-)
> 
> diff --git a/ipc/msg.c b/ipc/msg.c
> index 3b6545302598..724000c15296 100644
> --- a/ipc/msg.c
> +++ b/ipc/msg.c
> @@ -805,7 +805,7 @@ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
>  	msq = msq_obtain_object_check(ns, msqid);
>  	if (IS_ERR(msq)) {
>  		err = PTR_ERR(msq);
> -		goto out_unlock1;
> +		goto out_unlock2;
>  	}
> 
>  	ipc_lock_object(&msq->q_perm);
> @@ -851,8 +851,12 @@ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
>  		rcu_read_lock();
>  		ipc_lock_object(&msq->q_perm);
> 
> -		ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
>  		/* raced with RMID? */
> +		if (!__ipc_rcu_putref(&msq->q_perm)) {
> +			ipc_unlock_object(&msq->q_perm);
> +			call_rcu(&msq->q_perm.rcu, msg_rcu_free);
> +			goto out_unlock1;
> +		}
>  		if (!ipc_valid_object(&msq->q_perm)) {
>  			err = -EIDRM;
>  			goto out_unlock0;
> @@ -883,8 +887,9 @@ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
> 
>  out_unlock0:
>  	ipc_unlock_object(&msq->q_perm);
> -	wake_up_q(&wake_q);
>  out_unlock1:
> +	wake_up_q(&wake_q);
> +out_unlock2:
>  	rcu_read_unlock();
>  	if (msg != NULL)
>  		free_msg(msg);
> diff --git a/ipc/sem.c b/ipc/sem.c
> index 5af1943ad782..c269fae05b24 100644
> --- a/ipc/sem.c
> +++ b/ipc/sem.c
> @@ -475,10 +475,16 @@ static inline struct sem_array *sem_obtain_object_check(struct ipc_namespace *ns
>  	return container_of(ipcp, struct sem_array, sem_perm);
>  }
> 
> -static inline void sem_lock_and_putref(struct sem_array *sma)
> +static int __must_check sem_lock_and_putref(struct sem_array *sma)
>  {
>  	sem_lock(sma, NULL, -1);
> -	ipc_rcu_putref(&sma->sem_perm, sem_rcu_free);
> +
> +	if (!__ipc_rcu_putref(&sma->sem_perm)) {
> +		sem_unlock(sma, -1);
> +		call_rcu(&sma->sem_perm.rcu, sem_rcu_free);
> +		return 0;
> +	}
> +	return 1;
>  }
> 
>  static inline void sem_rmid(struct ipc_namespace *ns, struct sem_array *s)
> @@ -1434,7 +1440,10 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
>  			}
> 
>  			rcu_read_lock();
> -			sem_lock_and_putref(sma);
> +			if (!sem_lock_and_putref(sma)) {
> +				goto out_rcu_wakeup;
> +			}
> +
>  			if (!ipc_valid_object(&sma->sem_perm)) {
>  				err = -EIDRM;
>  				goto out_unlock;
> @@ -1483,7 +1492,11 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
>  			}
>  		}
>  		rcu_read_lock();
> -		sem_lock_and_putref(sma);
> +		if (!sem_lock_and_putref(sma)) {
> +			err = -EIDRM;
> +			goto out_rcu_wakeup;
> +		}
> +
>  		if (!ipc_valid_object(&sma->sem_perm)) {
>  			err = -EIDRM;
>  			goto out_unlock;
> @@ -1898,14 +1911,12 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid)
> 
>  	/* step 3: Acquire the lock on semaphore array */
>  	rcu_read_lock();
> -	sem_lock_and_putref(sma);
> -	if (!ipc_valid_object(&sma->sem_perm)) {
> -		sem_unlock(sma, -1);
> -		rcu_read_unlock();
> -		kfree(new);
> -		un = ERR_PTR(-EIDRM);
> -		goto out;
> -	}
> +	if (!sem_lock_and_putref(sma))
> +		goto out_EIDRM_free;
> +
> +	if (!ipc_valid_object(&sma->sem_perm))
> +		goto out_EIDRM_unlock;
> +
>  	spin_lock(&ulp->lock);
> 
>  	/*
> @@ -1931,6 +1942,13 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid)
>  	sem_unlock(sma, -1);
>  out:
>  	return un;
> +
> +out_EIDRM_unlock:
> +	sem_unlock(sma, -1);
> +out_EIDRM_free:
> +	rcu_read_unlock();
> +	kfree(new);
> +	return ERR_PTR(-EIDRM);
>  }
> 
>  static long do_semtimedop(int semid, struct sembuf __user *tsops,
> diff --git a/ipc/util.c b/ipc/util.c
> index 4e81182fa0ac..ba7f96fc8a61 100644
> --- a/ipc/util.c
> +++ b/ipc/util.c
> @@ -462,18 +462,47 @@ void ipc_set_key_private(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
>  	ipcp->key = IPC_PRIVATE;
>  }
> 
> -int ipc_rcu_getref(struct kern_ipc_perm *ptr)
> +int __must_check ipc_rcu_getref(struct kern_ipc_perm *ptr)
>  {
>  	return refcount_inc_not_zero(&ptr->refcount);
>  }
> 
> -void ipc_rcu_putref(struct kern_ipc_perm *ptr,
> +/**
> + * __ipc_rcu_putref - reduce reference count of an ipc object.
> + * @ptr: ipc object
> + *
> + * The function reduces the reference count of an ipc object by 1.
> + * If the reference count drops to 0, then the function returns 0.
> + * If this happens, then the caller is responsible for triggering
> + * call_rcu() to free the ipc object.
> + */
> +int __must_check __ipc_rcu_putref(struct kern_ipc_perm *ptr)
> +{
> +	if (!refcount_dec_and_test(&ptr->refcount))
> +		return 1;
> +	return 0;
> +}
> +
> +/**
> + * ipc_rcu_putref - reduce reference count of an ipc object.
> + * @ptr: ipc object
> + * @func: cleanup function to call when the reference is reduced to 0.
> + *
> + * The function reduces the reference count of an ipc object by 1.
> + * If the count drops to 0, then a call_rcu is triggered to free the ipc
> + * object.
> + *
> + * If the reference count drops to 0, then the function returns 0.
> + * If this happens, then the caller must not dereference ptr.
> + */
> +int ipc_rcu_putref(struct kern_ipc_perm *ptr,
>  			void (*func)(struct rcu_head *head))
>  {
>  	if (!refcount_dec_and_test(&ptr->refcount))
> -		return;
> +		return 1;
> 
>  	call_rcu(&ptr->rcu, func);
> +	return 0;
>  }
> 
>  /**
> diff --git a/ipc/util.h b/ipc/util.h
> index 0aba3230d007..fbd55aeee933 100644
> --- a/ipc/util.h
> +++ b/ipc/util.h
> @@ -134,12 +134,26 @@ static inline int ipc_get_maxid(struct ipc_ids *ids)
>   * Objects are reference counted, they start with reference count 1.
>   * getref increases the refcount, the putref call that reduces the recount
>   * to 0 schedules the rcu destruction. Caller must guarantee locking.
> + * As the reference count is reduced without holding any lock, this means
> + * that the caller must use ipc_valid_object() after acquiring ipc_lock_xx().
>   *
>   * refcount is initialized by ipc_addid(), before that point call_rcu()
>   * must be used.
> + *
> + * Most callers must check the return codes:
> + * - ipc_rcu_getref() fails if the reference count is already 0.
> + * - ipc_rcu_putref() calls call_rcu() if the reference count drops to 0,
> + *	dereferencing the ipc object if the function returns 0 is a
> + *	use-after-free.
> + * - with __ipc_rcu_putref(), the caller is responible for call_rcu().
> + *	This function is intended to be used if dereferencing must be done,
> + *	e.g. to drop a lock.
> + * ipc_rcu_putref does not use __must_check, because error paths or the
> + * IPC_RMID codepaths can safely ignore the return code.
>   */
> -int ipc_rcu_getref(struct kern_ipc_perm *ptr);
> -void ipc_rcu_putref(struct kern_ipc_perm *ptr,
> +int __must_check ipc_rcu_getref(struct kern_ipc_perm *ptr);
> +int __must_check __ipc_rcu_putref(struct kern_ipc_perm *ptr);
> +int ipc_rcu_putref(struct kern_ipc_perm *ptr,
>  			void (*func)(struct rcu_head *head));
> 
>  struct kern_ipc_perm *ipc_lock(struct ipc_ids *, int);
> -- 
> 2.14.4
> 


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2018-07-04 14:43 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-04 11:53 [RFC] ipc: refcounting / use after free? Manfred Spraul
2018-07-04 14:45 ` Paul E. McKenney

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).