diff mbox series

[v2] smackfs: restrict bytes count in smackfs write functions

Message ID 20210128115801.1096425-1-snovitoll@gmail.com (mailing list archive)
State New, archived
Headers show
Series [v2] smackfs: restrict bytes count in smackfs write functions | expand

Commit Message

Sabyrzhan Tasbolatov Jan. 28, 2021, 11:58 a.m. UTC
syzbot found WARNINGs in several smackfs write operations where
bytes count is passed to memdup_user_nul which exceeds
GFP MAX_ORDER. Check count size if bigger than PAGE_SIZE.

Per smackfs doc, smk_write_net4addr accepts any label or -CIPSO,
smk_write_net6addr accepts any label or -DELETE. I couldn't find
any general rule for other label lengths except SMK_LABELLEN,
SMK_LONGLABEL, SMK_CIPSOMAX which are documented.

Let's constrain, in general, smackfs label lengths for PAGE_SIZE.
Although fuzzer crashes write to smackfs/netlabel on 0x400000 length.

Here is a quick way to reproduce the WARNING:
python -c "print('A' * 0x400000)" > /sys/fs/smackfs/netlabel

Reported-by: syzbot+a71a442385a0b2815497@syzkaller.appspotmail.com
Signed-off-by: Sabyrzhan Tasbolatov <snovitoll@gmail.com>
---
 security/smack/smackfs.c | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

Comments

Tetsuo Handa Jan. 28, 2021, 12:59 p.m. UTC | #1
On 2021/01/28 20:58, Sabyrzhan Tasbolatov wrote:
> @@ -2005,6 +2009,9 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
>  	if (!smack_privileged(CAP_MAC_ADMIN))
>  		return -EPERM;
>  
> +	if (count > PAGE_SIZE)
> +		return -EINVAL;
> +
>  	data = memdup_user_nul(buf, count);
>  	if (IS_ERR(data))
>  		return PTR_ERR(data);
> @@ -2740,10 +2754,13 @@ static ssize_t smk_write_relabel_self(struct file *file, const char __user *buf,
>  		return -EPERM;
>  
>  	/*
> +	 * No partial write.
>  	 * Enough data must be present.
>  	 */
>  	if (*ppos != 0)
>  		return -EINVAL;
> +	if (count == 0 || count > PAGE_SIZE)
> +		return -EINVAL;
>  
>  	data = memdup_user_nul(buf, count);
>  	if (IS_ERR(data))
> 

Doesn't this change break legitimate requests like

  char buffer[20000];

  memset(buffer, ' ', sizeof(buffer));
  memcpy(buffer + sizeof(buffer) - 10, "foo", 3);
  write(fd, buffer, sizeof(buffer));

?
Sabyrzhan Tasbolatov Jan. 28, 2021, 1:27 p.m. UTC | #2
> >  	/*
> > +	 * No partial write.
> >  	 * Enough data must be present.
> >  	 */
> >  	if (*ppos != 0)
> >  		return -EINVAL;
> > +	if (count == 0 || count > PAGE_SIZE)
> > +		return -EINVAL;
> >  
> >  	data = memdup_user_nul(buf, count);
> >  	if (IS_ERR(data))
> > 
> 
> Doesn't this change break legitimate requests like
> 
>   char buffer[20000];
> 
>   memset(buffer, ' ', sizeof(buffer));
>   memcpy(buffer + sizeof(buffer) - 10, "foo", 3);
>   write(fd, buffer, sizeof(buffer));
> 
> ?

It does, in this case. Then I need to patch another version with
whitespace stripping before, after label. I just followed the same thing
that I see in security/selinux/selinuxfs.c sel_write_enforce() etc.

It has the same memdup_user_nul() and count >= PAGE_SIZE check prior to that.
Tetsuo Handa Jan. 28, 2021, 2:24 p.m. UTC | #3
On 2021/01/28 22:27, Sabyrzhan Tasbolatov wrote:
>> Doesn't this change break legitimate requests like
>>
>>   char buffer[20000];
>>
>>   memset(buffer, ' ', sizeof(buffer));
>>   memcpy(buffer + sizeof(buffer) - 10, "foo", 3);
>>   write(fd, buffer, sizeof(buffer));
>>
>> ?
> 
> It does, in this case. Then I need to patch another version with
> whitespace stripping before, after label. I just followed the same thing
> that I see in security/selinux/selinuxfs.c sel_write_enforce() etc.
> 
> It has the same memdup_user_nul() and count >= PAGE_SIZE check prior to that.

Since sel_write_enforce() accepts string representation of an integer value, PAGE_SIZE is sufficient.
But since smk_write_onlycap() and smk_write_relabel_self() accept list of space-delimited words,
you need to prove why PAGE_SIZE does not break userspace in your patch.

Also, due to the "too small to fail" memory-allocation rule, memdup_user_nul() for
count < PAGE_SIZE * 8 bytes is "never fails with -ENOMEM unless SIGKILLed by the OOM
killer". Also, memdup_user_nul() for count >= PAGE_SIZE * (1 << MAX_ORDER) - 1 bytes is
"never succeeds". Thus, you can safely add

	if (count >= PAGE_SIZE * (1 << MAX_ORDER) - 1)
		return -EINVAL; // or -ENOMEM if you want compatibility

to smackfs write functions. But it is a strange requirement that the caller of
memdup_user_nul() has to be aware of upper limit in a way that we won't hit

	/*
	 * There are several places where we assume that the order value is sane
	 * so bail out early if the request is out of bound.
	 */
	if (unlikely(order >= MAX_ORDER)) {
		WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
		return NULL;
	}

path. memdup_user_nul() side should do

	if (count >= PAGE_SIZE * (1 << MAX_ORDER) - 1)
		return -ENOMEM;

check and return -ENOMEM if memdup_user_nul() does not want to use __GFP_NOWARN.
I still believe that memdup_user_nul() side should be fixed.
Casey Schaufler Jan. 29, 2021, 2:10 a.m. UTC | #4
On 1/28/2021 6:24 AM, Tetsuo Handa wrote:
> On 2021/01/28 22:27, Sabyrzhan Tasbolatov wrote:
>>> Doesn't this change break legitimate requests like
>>>
>>>   char buffer[20000];
>>>
>>>   memset(buffer, ' ', sizeof(buffer));
>>>   memcpy(buffer + sizeof(buffer) - 10, "foo", 3);
>>>   write(fd, buffer, sizeof(buffer));
>>>
>>> ?
>> It does, in this case. Then I need to patch another version with
>> whitespace stripping before, after label. I just followed the same thing
>> that I see in security/selinux/selinuxfs.c sel_write_enforce() etc.
>>
>> It has the same memdup_user_nul() and count >= PAGE_SIZE check prior to that.
> Since sel_write_enforce() accepts string representation of an integer value, PAGE_SIZE is sufficient.
> But since smk_write_onlycap() and smk_write_relabel_self() accept list of space-delimited words,
> you need to prove why PAGE_SIZE does not break userspace in your patch.

if PAGE_SIZE >= SMK_LOADSIZE all legitimate requests can be made
using PAGE_SIZE as a limit. Your example with 19990 spaces before
the data demonstrates that the interface is inadequately documented.
Tizen and Automotive Grade Linux are going to be fine with a PAGE_SIZE
limit. The best way to address this concern is to go ahead with the
PAGE_SIZE limit and create ABI documents for the smackfs interfaces.
I will take your patch for the former and create a patch for the latter.


>
> Also, due to the "too small to fail" memory-allocation rule, memdup_user_nul() for
> count < PAGE_SIZE * 8 bytes is "never fails with -ENOMEM unless SIGKILLed by the OOM
> killer". Also, memdup_user_nul() for count >= PAGE_SIZE * (1 << MAX_ORDER) - 1 bytes is
> "never succeeds". Thus, you can safely add
>
> 	if (count >= PAGE_SIZE * (1 << MAX_ORDER) - 1)
> 		return -EINVAL; // or -ENOMEM if you want compatibility
>
> to smackfs write functions. But it is a strange requirement that the caller of
> memdup_user_nul() has to be aware of upper limit in a way that we won't hit
>
> 	/*
> 	 * There are several places where we assume that the order value is sane
> 	 * so bail out early if the request is out of bound.
> 	 */
> 	if (unlikely(order >= MAX_ORDER)) {
> 		WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
> 		return NULL;
> 	}
>
> path. memdup_user_nul() side should do
>
> 	if (count >= PAGE_SIZE * (1 << MAX_ORDER) - 1)
> 		return -ENOMEM;
>
> check and return -ENOMEM if memdup_user_nul() does not want to use __GFP_NOWARN.
> I still believe that memdup_user_nul() side should be fixed.
>
Sabyrzhan Tasbolatov Feb. 2, 2021, 7:13 p.m. UTC | #5
> if PAGE_SIZE >= SMK_LOADSIZE all legitimate requests can be made
> using PAGE_SIZE as a limit. Your example with 19990 spaces before
> the data demonstrates that the interface is inadequately documented.
> Tizen and Automotive Grade Linux are going to be fine with a PAGE_SIZE
> limit. The best way to address this concern is to go ahead with the
> PAGE_SIZE limit and create ABI documents for the smackfs interfaces.
> I will take your patch for the former and create a patch for the latter.

Please let me know if there is anything else required for this patch.
AFAIU, we agreed with PAGE_SIZE as the limit.
Casey Schaufler Feb. 2, 2021, 7:33 p.m. UTC | #6
On 2/2/2021 11:13 AM, Sabyrzhan Tasbolatov wrote:
>> if PAGE_SIZE >= SMK_LOADSIZE all legitimate requests can be made
>> using PAGE_SIZE as a limit. Your example with 19990 spaces before
>> the data demonstrates that the interface is inadequately documented.
>> Tizen and Automotive Grade Linux are going to be fine with a PAGE_SIZE
>> limit. The best way to address this concern is to go ahead with the
>> PAGE_SIZE limit and create ABI documents for the smackfs interfaces.
>> I will take your patch for the former and create a patch for the latter.
> Please let me know if there is anything else required for this patch.
> AFAIU, we agreed with PAGE_SIZE as the limit.

I am in the process of adding your patch to smack-next for 5.12.

I will have a separate documentation patch.
diff mbox series

Patch

diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index 5d44b7d258ef..22ded2c26089 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -1167,7 +1167,7 @@  static ssize_t smk_write_net4addr(struct file *file, const char __user *buf,
 		return -EPERM;
 	if (*ppos != 0)
 		return -EINVAL;
-	if (count < SMK_NETLBLADDRMIN)
+	if (count < SMK_NETLBLADDRMIN || count > PAGE_SIZE - 1)
 		return -EINVAL;
 
 	data = memdup_user_nul(buf, count);
@@ -1427,7 +1427,7 @@  static ssize_t smk_write_net6addr(struct file *file, const char __user *buf,
 		return -EPERM;
 	if (*ppos != 0)
 		return -EINVAL;
-	if (count < SMK_NETLBLADDRMIN)
+	if (count < SMK_NETLBLADDRMIN || count > PAGE_SIZE - 1)
 		return -EINVAL;
 
 	data = memdup_user_nul(buf, count);
@@ -1834,6 +1834,10 @@  static ssize_t smk_write_ambient(struct file *file, const char __user *buf,
 	if (!smack_privileged(CAP_MAC_ADMIN))
 		return -EPERM;
 
+	/* Enough data must be present */
+	if (count == 0 || count > PAGE_SIZE)
+		return -EINVAL;
+
 	data = memdup_user_nul(buf, count);
 	if (IS_ERR(data))
 		return PTR_ERR(data);
@@ -2005,6 +2009,9 @@  static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
 	if (!smack_privileged(CAP_MAC_ADMIN))
 		return -EPERM;
 
+	if (count > PAGE_SIZE)
+		return -EINVAL;
+
 	data = memdup_user_nul(buf, count);
 	if (IS_ERR(data))
 		return PTR_ERR(data);
@@ -2092,6 +2099,9 @@  static ssize_t smk_write_unconfined(struct file *file, const char __user *buf,
 	if (!smack_privileged(CAP_MAC_ADMIN))
 		return -EPERM;
 
+	if (count > PAGE_SIZE)
+		return -EINVAL;
+
 	data = memdup_user_nul(buf, count);
 	if (IS_ERR(data))
 		return PTR_ERR(data);
@@ -2648,6 +2658,10 @@  static ssize_t smk_write_syslog(struct file *file, const char __user *buf,
 	if (!smack_privileged(CAP_MAC_ADMIN))
 		return -EPERM;
 
+	/* Enough data must be present */
+	if (count == 0 || count > PAGE_SIZE)
+		return -EINVAL;
+
 	data = memdup_user_nul(buf, count);
 	if (IS_ERR(data))
 		return PTR_ERR(data);
@@ -2740,10 +2754,13 @@  static ssize_t smk_write_relabel_self(struct file *file, const char __user *buf,
 		return -EPERM;
 
 	/*
+	 * No partial write.
 	 * Enough data must be present.
 	 */
 	if (*ppos != 0)
 		return -EINVAL;
+	if (count == 0 || count > PAGE_SIZE)
+		return -EINVAL;
 
 	data = memdup_user_nul(buf, count);
 	if (IS_ERR(data))