Message ID | 1580383064-16536-1-git-send-email-vjitta@codeaurora.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | mm: slub: reinitialize random sequence cache on slab object update | expand |
On 1/30/20 12:17 PM, vjitta@codeaurora.org wrote: > From: Vijayanand Jitta <vjitta@codeaurora.org> > > Random sequence cache is precomputed during slab object creation > based up on the object size and no of objects per slab. These could > be changed when flags like SLAB_STORE_USER, SLAB_POISON are updated > from sysfs. So when shuffle_freelist is called during slab_alloc it > uses updated object count to access the precomputed random sequence > cache. This could result in incorrect access of the random sequence > cache which could further result in slab corruption. Fix this by > reinitializing the random sequence cache up on slab object update. > > A sample panic trace when write to slab_store_user was attempted. A more complete oops report would have been better, e.g. if anyone was googling it, to find this patch. Also I was checking where else calculate_sizes() is called and found order_store(). So if somebody changes (especially increases) the order, shouldn't the reinitialization also be done? This is even more nasty as it doesn't seem to require that no objects exist. Also there is no synchronization against concurrent allocations/frees? Gasp.
On 2020-02-27 22:23, Vlastimil Babka wrote: > On 1/30/20 12:17 PM, vjitta@codeaurora.org wrote: >> From: Vijayanand Jitta <vjitta@codeaurora.org> >> >> Random sequence cache is precomputed during slab object creation >> based up on the object size and no of objects per slab. These could >> be changed when flags like SLAB_STORE_USER, SLAB_POISON are updated >> from sysfs. So when shuffle_freelist is called during slab_alloc it >> uses updated object count to access the precomputed random sequence >> cache. This could result in incorrect access of the random sequence >> cache which could further result in slab corruption. Fix this by >> reinitializing the random sequence cache up on slab object update. >> >> A sample panic trace when write to slab_store_user was attempted. > > A more complete oops report would have been better, e.g. if anyone was > googling > it, to find this patch. > > Also I was checking where else calculate_sizes() is called and found > order_store(). So if somebody changes (especially increases) the order, > shouldn't the reinitialization also be done? Yes, reinitialization must be done here aswell , will update the patch. > > This is even more nasty as it doesn't seem to require that no objects > exist. > Also there is no synchronization against concurrent allocations/frees? > Gasp. Since, random sequence cache is only used to update the freelist in shuffle_freelist which is done only when a new slab is created incase if objects allocations are done without a need of new slab creation they will use the existing freelist which should be fine as object size doesn't change after order_store() and incase if a new slab is created we will get the updated freelist. so in both cases i think it should be fine.
On 3/5/20 6:48 AM, vjitta@codeaurora.org wrote: > On 2020-02-27 22:23, Vlastimil Babka wrote: >> >> This is even more nasty as it doesn't seem to require that no objects >> exist. >> Also there is no synchronization against concurrent allocations/frees? >> Gasp. > > Since, random sequence cache is only used to update the freelist in > shuffle_freelist > which is done only when a new slab is created incase if objects > allocations are > done without a need of new slab creation they will use the existing > freelist which > should be fine as object size doesn't change after order_store() and > incase if a new > slab is created we will get the updated freelist. so in both cases i > think it should > be fine. I have some doubts. With reinit_cache_random_seq() for SLUB, s->random_seq will in turn: cache_random_seq_destroy() - point to an object that's been kfree'd - point to NULL init_cache_random_seq() cache_random_seq_create() - point to freshly allocated zeroed out object freelist_randomize() - the object is gradually initialized - the indices are gradually transformed to page offsets At any point of this, new slab can be allocated in parallel and observe s->random_seq in shuffle_freelist(), and it's only ok if it's currently NULL. Could it be fixed? In the reinit part you would need to - atomically update a valid s->random_seq to another valid s->random_seq (perhaps with NULL in between which means some freelist won't be perhaps randomized) - write barrier - call calculate_sizes() with updated flags / new order, make sure all the fields of s-> are updated in a safe order and with write barries (i.e. update s->oo and s->flags would be probably last, but maybe that's not all) so that anyone allocating a new slab will always get something valid (maybe that path would need also new read barriers?) No, I don't think it's worth the trouble?
diff --git a/mm/slub.c b/mm/slub.c index 0ab92ec..b88dd0f 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -1533,6 +1533,24 @@ static int init_cache_random_seq(struct kmem_cache *s) return 0; } +/* re-initialize the random sequence cache */ +static int reinit_cache_random_seq(struct kmem_cache *s) +{ + int err; + + if (s->random_seq) { + cache_random_seq_destroy(s); + err = init_cache_random_seq(s); + + if (err) { + pr_err("SLUB: Unable to re-initialize random sequence cache for %s\n", + s->name); + return err; + } + } + + return 0; +} /* Initialize each random sequence freelist per cache */ static void __init init_freelist_randomization(void) { @@ -1607,6 +1625,10 @@ static inline int init_cache_random_seq(struct kmem_cache *s) { return 0; } +static int reinit_cache_random_seq(struct kmem_cache *s) +{ + return 0; +} static inline void init_freelist_randomization(void) { } static inline bool shuffle_freelist(struct kmem_cache *s, struct page *page) { @@ -5192,6 +5214,7 @@ static ssize_t red_zone_store(struct kmem_cache *s, s->flags |= SLAB_RED_ZONE; } calculate_sizes(s, -1); + reinit_cache_random_seq(s); return length; } SLAB_ATTR(red_zone); @@ -5212,6 +5235,7 @@ static ssize_t poison_store(struct kmem_cache *s, s->flags |= SLAB_POISON; } calculate_sizes(s, -1); + reinit_cache_random_seq(s); return length; } SLAB_ATTR(poison); @@ -5233,6 +5257,7 @@ static ssize_t store_user_store(struct kmem_cache *s, s->flags |= SLAB_STORE_USER; } calculate_sizes(s, -1); + reinit_cache_random_seq(s); return length; } SLAB_ATTR(store_user);