diff mbox series

[1/3] coredump: Fixes core_pipe_limit sysctl proc_handler

Message ID 20241112131357.49582-2-nicolas.bouchinet@clip-os.org (mailing list archive)
State New
Headers show
Series Fixes multiple sysctl proc_handler usage error | expand

Commit Message

Nicolas Bouchinet Nov. 12, 2024, 1:13 p.m. UTC
From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>

proc_dointvec converts a string to a vector of signed int, which is
stored in the unsigned int .data core_pipe_limit.
It was thus authorized to write a negative value to core_pipe_limit
sysctl which once stored in core_pipe_limit, leads to the signed int
dump_count check against core_pipe_limit never be true. The same can be
achieved with core_pipe_limit set to INT_MAX.

Any negative write or >= to INT_MAX in core_pipe_limit sysctl would
hypothetically allow a user to create very high load on the system by
running processes that produces a coredump in case the core_pattern
sysctl is configured to pipe core files to user space helper.
Memory or PID exhaustion should happen before but it anyway breaks the
core_pipe_limit semantic

This commit fixes this by changing core_pipe_limit sysctl's proc_handler
to proc_dointvec_minmax and bound checking between SYSCTL_ZERO and
SYSCTL_INT_MAX.

Fixes: a293980c2e26 ("exec: let do_coredump() limit the number of concurrent dumps to pipes")
Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
---
 fs/coredump.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

Comments

Lin Feng Nov. 13, 2024, 2:35 a.m. UTC | #1
Hi,

see comments below please.

On 11/12/24 21:13, nicolas.bouchinet@clip-os.org wrote:
> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> 
> proc_dointvec converts a string to a vector of signed int, which is
> stored in the unsigned int .data core_pipe_limit.
> It was thus authorized to write a negative value to core_pipe_limit
> sysctl which once stored in core_pipe_limit, leads to the signed int
> dump_count check against core_pipe_limit never be true. The same can be
> achieved with core_pipe_limit set to INT_MAX.
> 
> Any negative write or >= to INT_MAX in core_pipe_limit sysctl would
> hypothetically allow a user to create very high load on the system by
> running processes that produces a coredump in case the core_pattern
> sysctl is configured to pipe core files to user space helper.
> Memory or PID exhaustion should happen before but it anyway breaks the
> core_pipe_limit semantic
> 
> This commit fixes this by changing core_pipe_limit sysctl's proc_handler
> to proc_dointvec_minmax and bound checking between SYSCTL_ZERO and
> SYSCTL_INT_MAX.
> 
> Fixes: a293980c2e26 ("exec: let do_coredump() limit the number of concurrent dumps to pipes")
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
> ---
>  fs/coredump.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/coredump.c b/fs/coredump.c
> index 7f12ff6ad1d3e..8ea5896e518dd 100644
> --- a/fs/coredump.c
> +++ b/fs/coredump.c
> @@ -616,7 +616,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
>  		cprm.limit = RLIM_INFINITY;
>  
>  		dump_count = atomic_inc_return(&core_dump_count);
> -		if (core_pipe_limit && (core_pipe_limit < dump_count)) {
> +		if ((core_pipe_limit && (core_pipe_limit < dump_count)) ||
> +		    (core_pipe_limit && dump_count == INT_MAX)) {

While comparing between 'unsigned int' and 'signed int', C deems them both
to 'unsigned int', so as an insane user sets core_pipe_limit to INT_MAX,
and dump_count(signed int) does overflow INT_MAX, checking for 
'core_pipe_limit < dump_count' is passed, thus codes skips core dump.

So IMO it's enough after changing proc_handler to proc_dointvec_minmax.

Others in this patch:
Reviewed-by: Lin Feng <linf@wangsu.com>

>  			printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n",
>  			       task_tgid_vnr(current), current->comm);
>  			printk(KERN_WARNING "Skipping core dump\n");
> @@ -1024,7 +1025,9 @@ static struct ctl_table coredump_sysctls[] = {
>  		.data		= &core_pipe_limit,
>  		.maxlen		= sizeof(unsigned int),
>  		.mode		= 0644,
> -		.proc_handler	= proc_dointvec,
> +		.proc_handler	= proc_dointvec_minmax,
> +		.extra1		= SYSCTL_ZERO,
> +		.extra2		= SYSCTL_INT_MAX,
>  	},
>  	{
>  		.procname       = "core_file_note_size_limit",
Nicolas Bouchinet Nov. 13, 2024, 2:15 p.m. UTC | #2
Hi Lin,

Thanks for your review.

On 11/13/24 03:35, Lin Feng wrote:
> Hi,
>
> see comments below please.
>
> On 11/12/24 21:13, nicolas.bouchinet@clip-os.org wrote:
>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>
>> proc_dointvec converts a string to a vector of signed int, which is
>> stored in the unsigned int .data core_pipe_limit.
>> It was thus authorized to write a negative value to core_pipe_limit
>> sysctl which once stored in core_pipe_limit, leads to the signed int
>> dump_count check against core_pipe_limit never be true. The same can be
>> achieved with core_pipe_limit set to INT_MAX.
>>
>> Any negative write or >= to INT_MAX in core_pipe_limit sysctl would
>> hypothetically allow a user to create very high load on the system by
>> running processes that produces a coredump in case the core_pattern
>> sysctl is configured to pipe core files to user space helper.
>> Memory or PID exhaustion should happen before but it anyway breaks the
>> core_pipe_limit semantic
>>
>> This commit fixes this by changing core_pipe_limit sysctl's proc_handler
>> to proc_dointvec_minmax and bound checking between SYSCTL_ZERO and
>> SYSCTL_INT_MAX.
>>
>> Fixes: a293980c2e26 ("exec: let do_coredump() limit the number of concurrent dumps to pipes")
>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>> ---
>>   fs/coredump.c | 7 +++++--
>>   1 file changed, 5 insertions(+), 2 deletions(-)
>>
>> diff --git a/fs/coredump.c b/fs/coredump.c
>> index 7f12ff6ad1d3e..8ea5896e518dd 100644
>> --- a/fs/coredump.c
>> +++ b/fs/coredump.c
>> @@ -616,7 +616,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
>>   		cprm.limit = RLIM_INFINITY;
>>   
>>   		dump_count = atomic_inc_return(&core_dump_count);
>> -		if (core_pipe_limit && (core_pipe_limit < dump_count)) {
>> +		if ((core_pipe_limit && (core_pipe_limit < dump_count)) ||
>> +		    (core_pipe_limit && dump_count == INT_MAX)) {
> While comparing between 'unsigned int' and 'signed int', C deems them both
> to 'unsigned int', so as an insane user sets core_pipe_limit to INT_MAX,
> and dump_count(signed int) does overflow INT_MAX, checking for
> 'core_pipe_limit < dump_count' is passed, thus codes skips core dump.
>
> So IMO it's enough after changing proc_handler to proc_dointvec_minmax.

Indeed, but the dump_count == INT_MAX is not here to catch overflow but 
if both dump_count
and core_pipe_limit are equal to INT_MAX. core_pipe_limit will not be 
inferior to dump_count.
Or maybe I am missing something ?

I should factorize the test though, this is kind of ugly.

>
> Others in this patch:
> Reviewed-by: Lin Feng <linf@wangsu.com>
>
>>   			printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n",
>>   			       task_tgid_vnr(current), current->comm);
>>   			printk(KERN_WARNING "Skipping core dump\n");
>> @@ -1024,7 +1025,9 @@ static struct ctl_table coredump_sysctls[] = {
>>   		.data		= &core_pipe_limit,
>>   		.maxlen		= sizeof(unsigned int),
>>   		.mode		= 0644,
>> -		.proc_handler	= proc_dointvec,
>> +		.proc_handler	= proc_dointvec_minmax,
>> +		.extra1		= SYSCTL_ZERO,
>> +		.extra2		= SYSCTL_INT_MAX,
>>   	},
>>   	{
>>   		.procname       = "core_file_note_size_limit",
>
Lin Feng Nov. 14, 2024, 1:34 a.m. UTC | #3
Hi, 

On 11/13/24 22:15, Nicolas Bouchinet wrote:
> Hi Lin,
> 
> Thanks for your review.
> 
> On 11/13/24 03:35, Lin Feng wrote:
>> Hi,
>>
>> see comments below please.
>>
>> On 11/12/24 21:13, nicolas.bouchinet@clip-os.org wrote:
>>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>>
>>> proc_dointvec converts a string to a vector of signed int, which is
>>> stored in the unsigned int .data core_pipe_limit.
>>> It was thus authorized to write a negative value to core_pipe_limit
>>> sysctl which once stored in core_pipe_limit, leads to the signed int
>>> dump_count check against core_pipe_limit never be true. The same can be
>>> achieved with core_pipe_limit set to INT_MAX.
>>>
>>> Any negative write or >= to INT_MAX in core_pipe_limit sysctl would
>>> hypothetically allow a user to create very high load on the system by
>>> running processes that produces a coredump in case the core_pattern
>>> sysctl is configured to pipe core files to user space helper.
>>> Memory or PID exhaustion should happen before but it anyway breaks the
>>> core_pipe_limit semantic
>>>
>>> This commit fixes this by changing core_pipe_limit sysctl's proc_handler
>>> to proc_dointvec_minmax and bound checking between SYSCTL_ZERO and
>>> SYSCTL_INT_MAX.
>>>
>>> Fixes: a293980c2e26 ("exec: let do_coredump() limit the number of concurrent dumps to pipes")
>>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>> ---
>>>   fs/coredump.c | 7 +++++--
>>>   1 file changed, 5 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/fs/coredump.c b/fs/coredump.c
>>> index 7f12ff6ad1d3e..8ea5896e518dd 100644
>>> --- a/fs/coredump.c
>>> +++ b/fs/coredump.c
>>> @@ -616,7 +616,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
>>>   		cprm.limit = RLIM_INFINITY;
>>>   
>>>   		dump_count = atomic_inc_return(&core_dump_count);
>>> -		if (core_pipe_limit && (core_pipe_limit < dump_count)) {
>>> +		if ((core_pipe_limit && (core_pipe_limit < dump_count)) ||
>>> +		    (core_pipe_limit && dump_count == INT_MAX)) {
>> While comparing between 'unsigned int' and 'signed int', C deems them both
>> to 'unsigned int', so as an insane user sets core_pipe_limit to INT_MAX,
>> and dump_count(signed int) does overflow INT_MAX, checking for
>> 'core_pipe_limit < dump_count' is passed, thus codes skips core dump.
>>
>> So IMO it's enough after changing proc_handler to proc_dointvec_minmax.
> Indeed, but the dump_count == INT_MAX is not here to catch overflow but 
> if both dump_count
> and core_pipe_limit are equal to INT_MAX. core_pipe_limit will not be 
> inferior to dump_count.
> Or maybe I am missing something ?
> 
Extracted from man core:
       Since Linux 2.6.32, the /proc/sys/kernel/core_pipe_limit can be used to
       defend against this possibility.  The value in this  file  defines  how
       many  concurrent crashing processes may be piped to user-space programs
       in parallel.  If this value is exceeded, then those crashing  processes
       above  this  value are noted in the kernel log and their core dumps are
       skipped.

Since no spinlock protecting us, due to the concurrent running of
atomic_inc_return(&core_dump_count), even with the changing above
it's not guaranteed that core_dump_count can't exceed core_pipe_limit).
As you said, suppose both of them are equal to INT_MAX(0x7fffffff),
and before any dummping thread drops core_dump_count, one new thread
comes in then hits atomic_inc_return(&core_dump_count) and now
(unsigned int)core_dump_count is 0x80000000, but original codes checking
for core_pipe_limit still works as expected.

Please correct me if I'm wrong :)

Thanks,
linfeng
Nicolas Bouchinet Nov. 14, 2024, 2:25 p.m. UTC | #4
On 11/14/24 02:34, Lin Feng wrote:
> Hi,
>
> On 11/13/24 22:15, Nicolas Bouchinet wrote:
>> Hi Lin,
>>
>> Thanks for your review.
>>
>> On 11/13/24 03:35, Lin Feng wrote:
>>> Hi,
>>>
>>> see comments below please.
>>>
>>> On 11/12/24 21:13, nicolas.bouchinet@clip-os.org wrote:
>>>> From: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>>>
>>>> proc_dointvec converts a string to a vector of signed int, which is
>>>> stored in the unsigned int .data core_pipe_limit.
>>>> It was thus authorized to write a negative value to core_pipe_limit
>>>> sysctl which once stored in core_pipe_limit, leads to the signed int
>>>> dump_count check against core_pipe_limit never be true. The same can be
>>>> achieved with core_pipe_limit set to INT_MAX.
>>>>
>>>> Any negative write or >= to INT_MAX in core_pipe_limit sysctl would
>>>> hypothetically allow a user to create very high load on the system by
>>>> running processes that produces a coredump in case the core_pattern
>>>> sysctl is configured to pipe core files to user space helper.
>>>> Memory or PID exhaustion should happen before but it anyway breaks the
>>>> core_pipe_limit semantic
>>>>
>>>> This commit fixes this by changing core_pipe_limit sysctl's proc_handler
>>>> to proc_dointvec_minmax and bound checking between SYSCTL_ZERO and
>>>> SYSCTL_INT_MAX.
>>>>
>>>> Fixes: a293980c2e26 ("exec: let do_coredump() limit the number of concurrent dumps to pipes")
>>>> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@ssi.gouv.fr>
>>>> ---
>>>>    fs/coredump.c | 7 +++++--
>>>>    1 file changed, 5 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/fs/coredump.c b/fs/coredump.c
>>>> index 7f12ff6ad1d3e..8ea5896e518dd 100644
>>>> --- a/fs/coredump.c
>>>> +++ b/fs/coredump.c
>>>> @@ -616,7 +616,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
>>>>    		cprm.limit = RLIM_INFINITY;
>>>>    
>>>>    		dump_count = atomic_inc_return(&core_dump_count);
>>>> -		if (core_pipe_limit && (core_pipe_limit < dump_count)) {
>>>> +		if ((core_pipe_limit && (core_pipe_limit < dump_count)) ||
>>>> +		    (core_pipe_limit && dump_count == INT_MAX)) {
>>> While comparing between 'unsigned int' and 'signed int', C deems them both
>>> to 'unsigned int', so as an insane user sets core_pipe_limit to INT_MAX,
>>> and dump_count(signed int) does overflow INT_MAX, checking for
>>> 'core_pipe_limit < dump_count' is passed, thus codes skips core dump.
>>>
>>> So IMO it's enough after changing proc_handler to proc_dointvec_minmax.
>> Indeed, but the dump_count == INT_MAX is not here to catch overflow but
>> if both dump_count
>> and core_pipe_limit are equal to INT_MAX. core_pipe_limit will not be
>> inferior to dump_count.
>> Or maybe I am missing something ?
>>
> Extracted from man core:
>         Since Linux 2.6.32, the /proc/sys/kernel/core_pipe_limit can be used to
>         defend against this possibility.  The value in this  file  defines  how
>         many  concurrent crashing processes may be piped to user-space programs
>         in parallel.  If this value is exceeded, then those crashing  processes
>         above  this  value are noted in the kernel log and their core dumps are
>         skipped.
>
> Since no spinlock protecting us, due to the concurrent running of
> atomic_inc_return(&core_dump_count), even with the changing above
> it's not guaranteed that core_dump_count can't exceed core_pipe_limit).
> As you said, suppose both of them are equal to INT_MAX(0x7fffffff),
> and before any dummping thread drops core_dump_count, one new thread
> comes in then hits atomic_inc_return(&core_dump_count) and now
> (unsigned int)core_dump_count is 0x80000000, but original codes checking
> for core_pipe_limit still works as expected.

You are absolutely right about this. Moreover, as stated in my commit
message, pid or memory exhaustion should occur before reaching this
scenario.

Thank's for your comment and review. I'll fix and push a v2.

>
> Please correct me if I'm wrong :)
>
> Thanks,
> linfeng
>
>
diff mbox series

Patch

diff --git a/fs/coredump.c b/fs/coredump.c
index 7f12ff6ad1d3e..8ea5896e518dd 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -616,7 +616,8 @@  void do_coredump(const kernel_siginfo_t *siginfo)
 		cprm.limit = RLIM_INFINITY;
 
 		dump_count = atomic_inc_return(&core_dump_count);
-		if (core_pipe_limit && (core_pipe_limit < dump_count)) {
+		if ((core_pipe_limit && (core_pipe_limit < dump_count)) ||
+		    (core_pipe_limit && dump_count == INT_MAX)) {
 			printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n",
 			       task_tgid_vnr(current), current->comm);
 			printk(KERN_WARNING "Skipping core dump\n");
@@ -1024,7 +1025,9 @@  static struct ctl_table coredump_sysctls[] = {
 		.data		= &core_pipe_limit,
 		.maxlen		= sizeof(unsigned int),
 		.mode		= 0644,
-		.proc_handler	= proc_dointvec,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_INT_MAX,
 	},
 	{
 		.procname       = "core_file_note_size_limit",