| From: Jan Kara <jack@suse.cz> |
| Subject: mm: avoid overflows in dirty throttling logic |
| Date: Fri, 21 Jun 2024 16:42:38 +0200 |
| |
| The dirty throttling logic is interspersed with assumptions that dirty |
| limits in PAGE_SIZE units fit into 32-bit (so that various multiplications |
| fit into 64-bits). If limits end up being larger, we will hit overflows, |
| possible divisions by 0 etc. Fix these problems by never allowing so |
| large dirty limits as they have dubious practical value anyway. For |
| dirty_bytes / dirty_background_bytes interfaces we can just refuse to set |
| so large limits. For dirty_ratio / dirty_background_ratio it isn't so |
| simple as the dirty limit is computed from the amount of available memory |
| which can change due to memory hotplug etc. So when converting dirty |
| limits from ratios to numbers of pages, we just don't allow the result to |
| exceed UINT_MAX. |
| |
| This is root-only triggerable problem which occurs when the operator |
| sets dirty limits to >16 TB. |
| |
| Link: https://lkml.kernel.org/r/20240621144246.11148-2-jack@suse.cz |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Reported-by: Zach O'Keefe <zokeefe@google.com> |
| Reviewed-By: Zach O'Keefe <zokeefe@google.com> |
| Cc: <stable@vger.kernel.org> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| --- |
| |
| mm/page-writeback.c | 30 ++++++++++++++++++++++++++---- |
| 1 file changed, 26 insertions(+), 4 deletions(-) |
| |
| --- a/mm/page-writeback.c~mm-avoid-overflows-in-dirty-throttling-logic |
| +++ a/mm/page-writeback.c |
| @@ -415,13 +415,20 @@ static void domain_dirty_limits(struct d |
| else |
| bg_thresh = (bg_ratio * available_memory) / PAGE_SIZE; |
| |
| - if (bg_thresh >= thresh) |
| - bg_thresh = thresh / 2; |
| tsk = current; |
| if (rt_task(tsk)) { |
| bg_thresh += bg_thresh / 4 + global_wb_domain.dirty_limit / 32; |
| thresh += thresh / 4 + global_wb_domain.dirty_limit / 32; |
| } |
| + /* |
| + * Dirty throttling logic assumes the limits in page units fit into |
| + * 32-bits. This gives 16TB dirty limits max which is hopefully enough. |
| + */ |
| + if (thresh > UINT_MAX) |
| + thresh = UINT_MAX; |
| + /* This makes sure bg_thresh is within 32-bits as well */ |
| + if (bg_thresh >= thresh) |
| + bg_thresh = thresh / 2; |
| dtc->thresh = thresh; |
| dtc->bg_thresh = bg_thresh; |
| |
| @@ -471,7 +478,11 @@ static unsigned long node_dirty_limit(st |
| if (rt_task(tsk)) |
| dirty += dirty / 4; |
| |
| - return dirty; |
| + /* |
| + * Dirty throttling logic assumes the limits in page units fit into |
| + * 32-bits. This gives 16TB dirty limits max which is hopefully enough. |
| + */ |
| + return min_t(unsigned long, dirty, UINT_MAX); |
| } |
| |
| /** |
| @@ -508,10 +519,17 @@ static int dirty_background_bytes_handle |
| void *buffer, size_t *lenp, loff_t *ppos) |
| { |
| int ret; |
| + unsigned long old_bytes = dirty_background_bytes; |
| |
| ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos); |
| - if (ret == 0 && write) |
| + if (ret == 0 && write) { |
| + if (DIV_ROUND_UP(dirty_background_bytes, PAGE_SIZE) > |
| + UINT_MAX) { |
| + dirty_background_bytes = old_bytes; |
| + return -ERANGE; |
| + } |
| dirty_background_ratio = 0; |
| + } |
| return ret; |
| } |
| |
| @@ -537,6 +555,10 @@ static int dirty_bytes_handler(struct ct |
| |
| ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos); |
| if (ret == 0 && write && vm_dirty_bytes != old_bytes) { |
| + if (DIV_ROUND_UP(vm_dirty_bytes, PAGE_SIZE) > UINT_MAX) { |
| + vm_dirty_bytes = old_bytes; |
| + return -ERANGE; |
| + } |
| writeback_set_ratelimit(); |
| vm_dirty_ratio = 0; |
| } |
| _ |