Difference between revisions of "Kernel Protections/refcount t"

From Linux Kernel Security Subsystem
Jump to navigation Jump to search
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
= Summary =  
= Summary =  


HARDENED_ATOMIC 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].
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: <code>refcount_t</code>.  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 is the kernel reference counting API.
The following is the kernel reference counting API.

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