Difference between revisions of "Kernel Protections/refcount t"

From Linux Kernel Security Subsystem
Jump to navigation Jump to search
m (Minor language change in Summary)
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
= Summary =  
= Summary =  


HARDENED_ATOMIC is a kernel self-protection mechanism that greatly helps with the mitigation of [[Bug Classes/Use after free|use-after-free]] bugs.  It is based off of work done by the [https://pax.grsecurity.net PaX Team], originally called [https://forums.grsecurity.net/viewtopic.php?f=7&t=4173 PAX_REFCOUNT].
The refcount_t API is a kernel self-protection mechanism that greatly helps with the prevention of [[Bug Classes/Use after free|use-after-free]] bugs.  It is based off of work done by the [https://pax.grsecurity.net PaX Team], originally called [https://forums.grsecurity.net/viewtopic.php?f=7&t=4173 PAX_REFCOUNT].


= Reference Counting API =
= Reference Counting API =


HARDENED_ATOMIC introduces a new data type: <tt>refcount_t</tt>.  This type is to be used for all kernel reference counters.   
Instead of the traditional <code>atomic_t</code>, reference counting uses a new data type: <code>refcount_t</code>.  This type is to be used for all kernel reference counters.   


The following operations are the kernel reference counting API.  Please note that all operations are atomic, unless otherwise specified.  
The following is the kernel reference counting API.


;'''<tt>REFCOUNT_INIT(unsigned int)</tt>'''
;'''<code>REFCOUNT_INIT(unsigned int)</code>'''
: Initialize a <tt>refcount_t</tt> object.
: Initialize a <code>refcount_t</code> object.


;'''<tt>void refcount_set(refcount_t *, unsigned int)</tt>'''
;'''<code>void refcount_set(refcount_t *, unsigned int)</code>'''
: Set a <tt>refcount_t</tt> object's internal value.
: Set a <code>refcount_t</code> object's internal value.


;'''<tt>unsigned int refcount_read(refcount_t *)</tt>'''
;'''<code>unsigned int refcount_read(refcount_t *)</code>'''
: Returns the <tt>refcount_t</tt> object's internal value.
: Returns the <code>refcount_t</code> object's internal value.


;'''<tt>bool refcount_add_not_zero(unsigned int v, refcount_t *r)</tt>'''
;'''<code>bool refcount_add_not_zero(unsigned int v, refcount_t *r)</code>'''
: Add <tt>v</tt> to <tt>r</tt>.  If <tt>r + v</tt> causes an overflow, the result of the addition operation is not saved to <tt>r</tt>.  Returns <tt>true</tt> if the resulting value of <tt>r</tt> is non-zero, <tt>false</tt> otherwise.
: Add <code>v</code> to <code>r</code>.  If <code>r + v</code> causes an overflow, the result of the addition operation is not saved to <code>r</code>.  Returns <code>true</code> if the resulting value of <code>r</code> is non-zero, <code>false</code> otherwise.


;'''<tt>void refcount_add(unsigned int v, refcount_t *r)</tt>'''
;'''<code>void refcount_add(unsigned int v, refcount_t *r)</code>'''
: Adds <tt>v</tt> to <tt>r</tt> and stores the value in <tt>r</tt>.
: Adds <code>v</code> to <code>r</code> and stores the value in <code>r</code>.


;'''<tt>bool refcount_inc_not_zero(refcount_t *r)</tt>'''
;'''<code>bool refcount_inc_not_zero(refcount_t *r)</code>'''
: Increments <tt>r</tt> and tests whether <tt>r + 1</tt> causes an overflow.  If an overflow does occur, the result of the increment operation is not saved to <tt>r</tt>.  Will saturate at <tt>UINT_MAX</tt> and <tt>WARN</tt>. Returns <tt>true</tt> if the resulting value of <tt>r</tt> is non-zero, <tt>false</tt> otherwise.   
: Increments <code>r</code> and tests whether <code>r + 1</code> causes an overflow.  If an overflow does occur, the result of the increment operation is not saved to <code>r</code>.  Will saturate at <code>UINT_MAX</code> and <code>WARN</code>. Returns <code>true</code> if the resulting value of <code>r</code> is non-zero, <code>false</code> otherwise.   


;'''<tt>void refcount_inc(refcount_t *r)</tt>'''
;'''<code>void refcount_inc(refcount_t *r)</code>'''
: Increment <tt>r</tt>.  Will saturate at <tt>UINT_MAX</tt> and <tt>WARN</tt>.
: Increment <code>r</code>.  Will saturate at <code>UINT_MAX</code> and <code>WARN</code>.


;'''<tt>bool refcount_sub_and_test(unsigned int v, refcount_t *r)</tt>'''
;'''<code>bool refcount_sub_and_test(unsigned int v, refcount_t *r)</code>'''
: Subtract <tt>v</tt> from <tt>r</tt> and tests whether <tt>r - v</tt> causes an underflow.  If an underflow does occur, the result of the decrement operation is not saved to <tt>r</tt>.  Will fail to decrement when saturated at <tt>UINT_MAX</tt>.  Returns <tt>true</tt> if the resulting value of <tt>r</tt> is non-zero, <tt>false</tt> otherwise.
: Subtract <code>v</code> from <code>r</code> and tests whether <code>r - v</code> causes an underflow.  If an underflow does occur, the result of the decrement operation is not saved to <code>r</code>.  Will fail to decrement when saturated at <code>UINT_MAX</code>.  Returns <code>true</code> if the resulting value of <code>r</code> is non-zero, <code>false</code> otherwise.


;'''<tt>void refcount_dec(refcount_t *r)</tt>'''
;'''<code>void refcount_dec(refcount_t *r)</code>'''
: Decrement <tt>r</tt>.  If <tt>r - 1</tt> causes an underflow, the result of the decrement operation is not saved to <tt>r</tt>.  Will fail to decrement when saturated at <tt>UINT_MAX</tt>.
: Decrement <code>r</code>.  If <code>r - 1</code> causes an underflow, the result of the decrement operation is not saved to <code>r</code>.  Will fail to decrement when saturated at <code>UINT_MAX</code>.


;'''<tt>bool refcount_dec_if_one(refcount_t *r)</tt>'''
;'''<code>bool refcount_dec_if_one(refcount_t *r)</code>'''
: Attempts to transition <tt>r</tt> from 1 to 0.  If <tt>r</tt> is 1, decrement it to 0.  Returns <tt>true</tt> if <tt>r</tt> was decremented, <tt>false</tt> otherwise.   
: Attempts to transition <code>r</code> from 1 to 0.  If <code>r</code> is 1, decrement it to 0.  Returns <code>true</code> if <code>r</code> was decremented, <code>false</code> otherwise.   


;'''<tt>bool refcount_dec_not_one(refcount_t *r)</tt>'''
;'''<code>bool refcount_dec_not_one(refcount_t *r)</code>'''
: Decrement <tt>r</tt> unless the value of <tt>r</tt> is 1.  Returns <tt>true</tt> if <tt>r</tt> was decremented, </tt>false</tt> otherwise.
: Decrement <code>r</code> unless the value of <code>r</code> is 1.  Returns <code>true</code> if <code>r</code> was decremented, </code>false</code> otherwise.


;'''<tt>bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock)</tt>'''
;'''<code>bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock)</code>'''
: Decrement <tt>r</tt> and lock mutex if <tt>r</tt> becomes 0.  Will <tt>WARN</tt> on underflow and fail to decrement if <tt>r</tt> is saturated at <tt>UINT_MAX</tt>.  Returns <tt>true</tt> if <tt>r</tt> is 0 and mutex is held, <tt>false</tt> otherwise.
: Decrement <code>r</code> and lock mutex if <code>r</code> becomes 0.  Will <code>WARN</code> on underflow and fail to decrement if <code>r</code> is saturated at <code>UINT_MAX</code>.  Returns <code>true</code> if <code>r</code> is 0 and mutex is held, <code>false</code> otherwise.


;'''<tt>bool refcount_dec_and_lock(refcount_t *r, spinlock_t *s)</tt>'''
;'''<code>bool refcount_dec_and_lock(refcount_t *r, spinlock_t *s)</code>'''
: Decrement <tt>r</tt> and lock spinlock if <tt>r</tt> becomes 0.  Will <tt>WARN</tt> on underflow and fail to decrement if <tt>r</tt> is saturated at <tt>UINT_MAX</tt>.  Returns <tt>true</tt> if <tt>r</tt> is 0 and spinlock is held, <tt>false</tt> otherwise.
: Decrement <code>r</code> and lock spinlock if <code>r</code> becomes 0.  Will <code>WARN</code> on underflow and fail to decrement if <code>r</code> is saturated at <code>UINT_MAX</code>.  Returns <code>true</code> if <code>r</code> is 0 and spinlock is held, <code>false</code> otherwise.
 
= Examples =
 
The following use case is an instance of correct usage of the <code>refcount_t</code> API.  The object being counted is <code>struct super_block</code>, which represents a virtual filesystem superblock, an object containing a particular filesystem's metadata such as block size, the root inode, etc. 
 
==== Member Definition ====
 
This is the definition of the reference counter field in the <code>struct super_block</code> object.  If the object being counted is a structure, the reference counter is typically defined as a field of the counted structure, as we see in <code>struct super_block</code> below.
 
From <code>[http://lxr.free-electrons.com/source/include/linux/fs.h include/linux/fs.h]</code>:
 
<code>
    struct super_block {
        ...
        refcount_t s_active;
        ...
    };
</code>
 
==== Object Initialization ====
 
When a counted object is created, its reference counter must be initialized to something sane, typically 1 (since, by virtue of being called in an "allocation" method, a user of the object already exists). 
 
From <code>[http://lxr.free-electrons.com/source/fs/super.c fs/super.c]</code>:
 
<code>
    static struct super_block *alloc_super(struct file_system_type *type, int flags, struct user_namespace *user_ns)
    {
        struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER); 
        ...
        refcount_set(&s->s_active, 1);
        ...
    }
</code>
 
==== Getting a New Reference ====
This code is executed when a user wishes to obtain a new reference to a <code>struct super_block</code> object.  The following code corresponds to the traditional reference counting "get" method.
 
From <code>[http://lxr.free-electrons.com/source/fs/super.c fs/super.c]</code>:
 
<code>
    static int grab_super(struct super_block *s) __releases(sb_lock)
    {
        s->s_count++;
        spin_unlock(&sb_lock);
        down_write(&s->s_umount);
        if ((s->s_flags & MS_BORN) && refcount_inc_not_zero(&s->s_active)) {
            put_super(s);
            return 1;
        }
        up_write(&s->s_umount);
        put_super(s);
        return 0;
    }
</code>
 
==== Releasing an Existing Reference ====
This code is executed when a user currently holding a reference to a <code>struct super_block</code> object no longer needs the object and wants to release it.  The following code corresponds to the traditional reference counting "put" method. 
 
From <code>[http://lxr.free-electrons.com/source/fs/super.c fs/super.c]</code>:
 
<code>
    void deactivate_locked_super(struct super_block *s) {
        ...
        if (refcount_dec_and_test(&s->s_active)) {
            ...
            put_super(s);
        }
    }
 
    void deactivate_super(struct super_block *s)
    {
        if (!refcount_dec_not_one(&s->s_active)) {
            down_write(&s->s_umount);nnNnnn
            deactivate_locked_super(s);
        }
    }
</code>

Latest revision as of 05:20, 4 August 2017

Summary

The refcount_t API is a kernel self-protection mechanism that greatly helps with the prevention of use-after-free bugs. It is based off of work done by the PaX Team, originally called PAX_REFCOUNT.

Reference Counting API

Instead of the traditional atomic_t, reference counting uses a new data type: refcount_t. This type is to be used for all kernel reference counters.

The following is the kernel reference counting API.

REFCOUNT_INIT(unsigned int)
Initialize a refcount_t object.
void refcount_set(refcount_t *, unsigned int)
Set a refcount_t object's internal value.
unsigned int refcount_read(refcount_t *)
Returns the refcount_t object's internal value.
bool refcount_add_not_zero(unsigned int v, refcount_t *r)
Add v to r. If r + v causes an overflow, the result of the addition operation is not saved to r. Returns true if the resulting value of r is non-zero, false otherwise.
void refcount_add(unsigned int v, refcount_t *r)
Adds v to r and stores the value in r.
bool refcount_inc_not_zero(refcount_t *r)
Increments r and tests whether r + 1 causes an overflow. If an overflow does occur, the result of the increment operation is not saved to r. Will saturate at UINT_MAX and WARN. Returns true if the resulting value of r is non-zero, false otherwise.
void refcount_inc(refcount_t *r)
Increment r. Will saturate at UINT_MAX and WARN.
bool refcount_sub_and_test(unsigned int v, refcount_t *r)
Subtract v from r and tests whether r - v causes an underflow. If an underflow does occur, the result of the decrement operation is not saved to r. Will fail to decrement when saturated at UINT_MAX. Returns true if the resulting value of r is non-zero, false otherwise.
void refcount_dec(refcount_t *r)
Decrement r. If r - 1 causes an underflow, the result of the decrement operation is not saved to r. Will fail to decrement when saturated at UINT_MAX.
bool refcount_dec_if_one(refcount_t *r)
Attempts to transition r from 1 to 0. If r is 1, decrement it to 0. Returns true if r was decremented, false otherwise.
bool refcount_dec_not_one(refcount_t *r)
Decrement r unless the value of r is 1. Returns true if r was decremented, false otherwise.
bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock)
Decrement r and lock mutex if r becomes 0. Will WARN on underflow and fail to decrement if r is saturated at UINT_MAX. Returns true if r is 0 and mutex is held, false otherwise.
bool refcount_dec_and_lock(refcount_t *r, spinlock_t *s)
Decrement r and lock spinlock if r becomes 0. Will WARN on underflow and fail to decrement if r is saturated at UINT_MAX. Returns true if r is 0 and spinlock is held, false otherwise.

Examples

The following use case is an instance of correct usage of the refcount_t API. The object being counted is struct super_block, which represents a virtual filesystem superblock, an object containing a particular filesystem's metadata such as block size, the root inode, etc.

Member Definition

This is the definition of the reference counter field in the struct super_block object. If the object being counted is a structure, the reference counter is typically defined as a field of the counted structure, as we see in struct super_block below.

From include/linux/fs.h:

   struct super_block {
       ...
       refcount_t s_active;
       ...
   };

Object Initialization

When a counted object is created, its reference counter must be initialized to something sane, typically 1 (since, by virtue of being called in an "allocation" method, a user of the object already exists).

From fs/super.c:

   static struct super_block *alloc_super(struct file_system_type *type, int flags, struct user_namespace *user_ns)
   {
       struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER);   
       ...
       refcount_set(&s->s_active, 1);
       ...
   }

Getting a New Reference

This code is executed when a user wishes to obtain a new reference to a struct super_block object. The following code corresponds to the traditional reference counting "get" method.

From fs/super.c:

   static int grab_super(struct super_block *s) __releases(sb_lock)
   {
       s->s_count++;
       spin_unlock(&sb_lock);
       down_write(&s->s_umount);
       if ((s->s_flags & MS_BORN) && refcount_inc_not_zero(&s->s_active)) {
           put_super(s);
           return 1;
       }
       up_write(&s->s_umount);
       put_super(s);
       return 0;
   }

Releasing an Existing Reference

This code is executed when a user currently holding a reference to a struct super_block object no longer needs the object and wants to release it. The following code corresponds to the traditional reference counting "put" method.

From fs/super.c:

   void deactivate_locked_super(struct super_block *s) {
       ...
       if (refcount_dec_and_test(&s->s_active)) {
           ...
           put_super(s);
       }
   }
   void deactivate_super(struct super_block *s)
   {
       if (!refcount_dec_not_one(&s->s_active)) {
           down_write(&s->s_umount);nnNnnn
           deactivate_locked_super(s);
       }
   }