atomic_t variables are currently used to implement reference
counters with the following properties:
 - counter is initialized to 1 using atomic_set()
 - a resource is freed upon counter reaching zero
 - once counter reaches zero, its further
   increments aren't allowed
 - counter schema uses basic atomic operations
   (set, inc, inc_not_zero, dec_and_test, etc.)

Such atomic variables should be converted to a newly provided
refcount_t type and API that prevents accidental counter overflows
and underflows. This is important since overflows and underflows
can lead to use-after-free situation and be exploitable.

The variable task_struct.usage is used as pure reference counter.
Convert it to refcount_t and fix up the operations.

Suggested-by: Kees Cook <keesc...@chromium.org>
Reviewed-by: David Windsor <dwind...@gmail.com>
Reviewed-by: Hans Liljestrand <ishkam...@gmail.com>
Signed-off-by: Elena Reshetova <elena.reshet...@intel.com>
---
 include/linux/init_task.h  | 2 +-
 include/linux/sched.h      | 3 ++-
 include/linux/sched/task.h | 4 ++--
 kernel/fork.c              | 4 ++--
 4 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index a85376e..64d86ec 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -226,7 +226,7 @@ extern struct cred init_cred;
        INIT_TASK_TI(tsk)                                               \
        .state          = 0,                                            \
        .stack          = init_stack,                                   \
-       .usage          = ATOMIC_INIT(2),                               \
+       .usage          = REFCOUNT_INIT(2),                             \
        .flags          = PF_KTHREAD,                                   \
        .prio           = MAX_PRIO-20,                                  \
        .static_prio    = MAX_PRIO-20,                                  \
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 0f897df..47f1101 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -20,6 +20,7 @@
 #include <linux/seccomp.h>
 #include <linux/nodemask.h>
 #include <linux/rcupdate.h>
+#include <linux/refcount.h>
 #include <linux/resource.h>
 #include <linux/latencytop.h>
 #include <linux/sched/prio.h>
@@ -536,7 +537,7 @@ struct task_struct {
        randomized_struct_fields_start
 
        void                            *stack;
-       atomic_t                        usage;
+       refcount_t                      usage;
        /* Per task flags (PF_*), defined further below: */
        unsigned int                    flags;
        unsigned int                    ptrace;
diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h
index a5e6f09..ac4317d 100644
--- a/include/linux/sched/task.h
+++ b/include/linux/sched/task.h
@@ -85,13 +85,13 @@ extern void sched_exec(void);
 #define sched_exec()   {}
 #endif
 
-#define get_task_struct(tsk) do { atomic_inc(&(tsk)->usage); } while(0)
+#define get_task_struct(tsk) do { refcount_inc(&(tsk)->usage); } while(0)
 
 extern void __put_task_struct(struct task_struct *t);
 
 static inline void put_task_struct(struct task_struct *t)
 {
-       if (atomic_dec_and_test(&t->usage))
+       if (refcount_dec_and_test(&t->usage))
                __put_task_struct(t);
 }
 
diff --git a/kernel/fork.c b/kernel/fork.c
index 869850b..68cc7a0 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -649,7 +649,7 @@ static inline void put_signal_struct(struct signal_struct 
*sig)
 void __put_task_struct(struct task_struct *tsk)
 {
        WARN_ON(!tsk->exit_state);
-       WARN_ON(atomic_read(&tsk->usage));
+       WARN_ON(refcount_read(&tsk->usage));
        WARN_ON(tsk == current);
 
        cgroup_free(tsk);
@@ -824,7 +824,7 @@ static struct task_struct *dup_task_struct(struct 
task_struct *orig, int node)
         * One for us, one for whoever does the "release_task()" (usually
         * parent)
         */
-       atomic_set(&tsk->usage, 2);
+       refcount_set(&tsk->usage, 2);
 #ifdef CONFIG_BLK_DEV_IO_TRACE
        tsk->btrace_seq = 0;
 #endif
-- 
2.7.4

Reply via email to