Skip to content

Commit 6cdb18a

Browse files
heicarsttorvalds
authored andcommitted
mm/vmstat: fix overflow in mod_zone_page_state()
mod_zone_page_state() takes a "delta" integer argument. delta contains the number of pages that should be added or subtracted from a struct zone's vm_stat field. If a zone is larger than 8TB this will cause overflows. E.g. for a zone with a size slightly larger than 8TB the line mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages); in mm/page_alloc.c:free_area_init_core() will result in a negative result for the NR_ALLOC_BATCH entry within the zone's vm_stat, since 8TB contain 0x8xxxxxxx pages which will be sign extended to a negative value. Fix this by changing the delta argument to long type. This could fix an early boot problem seen on s390, where we have a 9TB system with only one node. ZONE_DMA contains 2GB and ZONE_NORMAL the rest. The system is trying to allocate a GFP_DMA page but ZONE_DMA is completely empty, so it tries to reclaim pages in an endless loop. This was seen on a heavily patched 3.10 kernel. One possible explaination seem to be the overflows caused by mod_zone_page_state(). Unfortunately I did not have the chance to verify that this patch actually fixes the problem, since I don't have access to the system right now. However the overflow problem does exist anyway. Given the description that a system with slightly less than 8TB does work, this seems to be a candidate for the observed problem. Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com> Cc: Christoph Lameter <cl@linux.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent cc28d6d commit 6cdb18a

File tree

2 files changed

+8
-8
lines changed

2 files changed

+8
-8
lines changed

include/linux/vmstat.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,11 @@ extern void zone_statistics(struct zone *, struct zone *, gfp_t gfp);
176176
#define sub_zone_page_state(__z, __i, __d) mod_zone_page_state(__z, __i, -(__d))
177177

178178
#ifdef CONFIG_SMP
179-
void __mod_zone_page_state(struct zone *, enum zone_stat_item item, int);
179+
void __mod_zone_page_state(struct zone *, enum zone_stat_item item, long);
180180
void __inc_zone_page_state(struct page *, enum zone_stat_item);
181181
void __dec_zone_page_state(struct page *, enum zone_stat_item);
182182

183-
void mod_zone_page_state(struct zone *, enum zone_stat_item, int);
183+
void mod_zone_page_state(struct zone *, enum zone_stat_item, long);
184184
void inc_zone_page_state(struct page *, enum zone_stat_item);
185185
void dec_zone_page_state(struct page *, enum zone_stat_item);
186186

@@ -205,7 +205,7 @@ void set_pgdat_percpu_threshold(pg_data_t *pgdat,
205205
* The functions directly modify the zone and global counters.
206206
*/
207207
static inline void __mod_zone_page_state(struct zone *zone,
208-
enum zone_stat_item item, int delta)
208+
enum zone_stat_item item, long delta)
209209
{
210210
zone_page_state_add(delta, zone, item);
211211
}

mm/vmstat.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ void set_pgdat_percpu_threshold(pg_data_t *pgdat,
219219
* particular counter cannot be updated from interrupt context.
220220
*/
221221
void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
222-
int delta)
222+
long delta)
223223
{
224224
struct per_cpu_pageset __percpu *pcp = zone->pageset;
225225
s8 __percpu *p = pcp->vm_stat_diff + item;
@@ -318,8 +318,8 @@ EXPORT_SYMBOL(__dec_zone_page_state);
318318
* 1 Overstepping half of threshold
319319
* -1 Overstepping minus half of threshold
320320
*/
321-
static inline void mod_state(struct zone *zone,
322-
enum zone_stat_item item, int delta, int overstep_mode)
321+
static inline void mod_state(struct zone *zone, enum zone_stat_item item,
322+
long delta, int overstep_mode)
323323
{
324324
struct per_cpu_pageset __percpu *pcp = zone->pageset;
325325
s8 __percpu *p = pcp->vm_stat_diff + item;
@@ -357,7 +357,7 @@ static inline void mod_state(struct zone *zone,
357357
}
358358

359359
void mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
360-
int delta)
360+
long delta)
361361
{
362362
mod_state(zone, item, delta, 0);
363363
}
@@ -384,7 +384,7 @@ EXPORT_SYMBOL(dec_zone_page_state);
384384
* Use interrupt disable to serialize counter updates
385385
*/
386386
void mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
387-
int delta)
387+
long delta)
388388
{
389389
unsigned long flags;
390390

0 commit comments

Comments
 (0)