[v2] netfilter: properly initialize xt_table_info structure

Message ID 20180517093410.GB17597@kroah.com
State Not Applicable
Delegated to: Pablo Neira
Headers show
Series
  • [v2] netfilter: properly initialize xt_table_info structure
Related show

Commit Message

Greg Kroah-Hartman May 17, 2018, 9:34 a.m.
When allocating a xt_table_info structure, we should be clearing out the
full amount of memory that was allocated, not just the "header" of the
structure.  Otherwise odd values could be passed to userspace, which is
not a good thing.

Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
v2: use kvzalloc instead of kvmalloc/memset pair, as suggested by Michal Kubecek

 net/netfilter/x_tables.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

Comments

Eric Dumazet May 17, 2018, 9:55 a.m. | #1
On 05/17/2018 02:34 AM, Greg Kroah-Hartman wrote:
> When allocating a xt_table_info structure, we should be clearing out the
> full amount of memory that was allocated, not just the "header" of the
> structure.  Otherwise odd values could be passed to userspace, which is
> not a good thing.
> 
> Cc: stable <stable@vger.kernel.org>
> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> ---
> v2: use kvzalloc instead of kvmalloc/memset pair, as suggested by Michal Kubecek
> 
>  net/netfilter/x_tables.c | 3 +--
>  1 file changed, 1 insertion(+), 2 deletions(-)
> 
> diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
> index cb7cb300c3bc..cd22bb9b66f3 100644
> --- a/net/netfilter/x_tables.c
> +++ b/net/netfilter/x_tables.c
> @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
>  	 * than shoot all processes down before realizing there is nothing
>  	 * more to reclaim.
>  	 */
> -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
>  	if (!info)
>  		return NULL;
>  
> -	memset(info, 0, sizeof(*info));
>  	info->size = size;
>  	return info;
>  }
> 

I am curious, what particular path does not later overwrite the whole zone ?

Do not get me wrong, this is not fast path, but these blobs can be huge.

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Greg Kroah-Hartman May 17, 2018, 10:09 a.m. | #2
On Thu, May 17, 2018 at 02:55:42AM -0700, Eric Dumazet wrote:
> 
> 
> On 05/17/2018 02:34 AM, Greg Kroah-Hartman wrote:
> > When allocating a xt_table_info structure, we should be clearing out the
> > full amount of memory that was allocated, not just the "header" of the
> > structure.  Otherwise odd values could be passed to userspace, which is
> > not a good thing.
> > 
> > Cc: stable <stable@vger.kernel.org>
> > Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> > ---
> > v2: use kvzalloc instead of kvmalloc/memset pair, as suggested by Michal Kubecek
> > 
> >  net/netfilter/x_tables.c | 3 +--
> >  1 file changed, 1 insertion(+), 2 deletions(-)
> > 
> > diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
> > index cb7cb300c3bc..cd22bb9b66f3 100644
> > --- a/net/netfilter/x_tables.c
> > +++ b/net/netfilter/x_tables.c
> > @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
> >  	 * than shoot all processes down before realizing there is nothing
> >  	 * more to reclaim.
> >  	 */
> > -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> >  	if (!info)
> >  		return NULL;
> >  
> > -	memset(info, 0, sizeof(*info));
> >  	info->size = size;
> >  	return info;
> >  }
> > 
> 
> I am curious, what particular path does not later overwrite the whole zone ?

The path back was long, adding Greg Hackman who helped to debug this to
the To: to confirm that I got this correct...

In do_ipt_get_ctl, the IPT_SO_GET_ENTRIES: option uses a len value that
can be larger than the size of the structure itself.

Then the data is copied to userspace in copy_entries_to_user() for ipv4
and v6, and that's where the "bad data" was noticed (a researcher was
using a kernel patch to determine what the data was)

Greg, that's the correct path here, right?

> Do not get me wrong, this is not fast path, but these blobs can be huge.

Yeah, I bet, but for "normal" cases the size should be small and all
should be fine.

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jan Engelhardt May 17, 2018, 10:42 a.m. | #3
On Thursday 2018-05-17 12:09, Greg Kroah-Hartman wrote:
>> > --- a/net/netfilter/x_tables.c
>> > +++ b/net/netfilter/x_tables.c
>> > @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
>> >  	 * than shoot all processes down before realizing there is nothing
>> >  	 * more to reclaim.
>> >  	 */
>> > -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
>> > +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
>> >  	if (!info)
>> >  		return NULL;
>>
>> I am curious, what particular path does not later overwrite the whole zone ?
>
>In do_ipt_get_ctl, the IPT_SO_GET_ENTRIES: option uses a len value that
>can be larger than the size of the structure itself.
>
>Then the data is copied to userspace in copy_entries_to_user() for ipv4
>and v6, and that's where the "bad data"

If the kernel incorrectly copies more bytes than it should, isn't that
a sign that may be going going past the end of the info buffer?
(And thus, zeroing won't truly fix the issue)

And if the kernel copies too few (because it just does not have more
data than userspace is requesting), what remains in the user buffer
is the garbage that originally was there.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Greg Kroah-Hartman May 17, 2018, 1:20 p.m. | #4
On Thu, May 17, 2018 at 12:42:00PM +0200, Jan Engelhardt wrote:
> 
> On Thursday 2018-05-17 12:09, Greg Kroah-Hartman wrote:
> >> > --- a/net/netfilter/x_tables.c
> >> > +++ b/net/netfilter/x_tables.c
> >> > @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
> >> >  	 * than shoot all processes down before realizing there is nothing
> >> >  	 * more to reclaim.
> >> >  	 */
> >> > -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> >> > +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> >> >  	if (!info)
> >> >  		return NULL;
> >>
> >> I am curious, what particular path does not later overwrite the whole zone ?
> >
> >In do_ipt_get_ctl, the IPT_SO_GET_ENTRIES: option uses a len value that
> >can be larger than the size of the structure itself.
> >
> >Then the data is copied to userspace in copy_entries_to_user() for ipv4
> >and v6, and that's where the "bad data"
> 
> If the kernel incorrectly copies more bytes than it should, isn't that
> a sign that may be going going past the end of the info buffer?
> (And thus, zeroing won't truly fix the issue)

No, the buffer size is correct, we just aren't filling up the whole
buffer as the data requested is smaller than the buffer size.

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Florian Westphal May 18, 2018, 9:27 a.m. | #5
Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote:
> On Thu, May 17, 2018 at 12:42:00PM +0200, Jan Engelhardt wrote:
> > 
> > On Thursday 2018-05-17 12:09, Greg Kroah-Hartman wrote:
> > >> > --- a/net/netfilter/x_tables.c
> > >> > +++ b/net/netfilter/x_tables.c
> > >> > @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
> > >> >  	 * than shoot all processes down before realizing there is nothing
> > >> >  	 * more to reclaim.
> > >> >  	 */
> > >> > -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > >> > +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > >> >  	if (!info)
> > >> >  		return NULL;
> > >>
> > >> I am curious, what particular path does not later overwrite the whole zone ?
> > >
> > >In do_ipt_get_ctl, the IPT_SO_GET_ENTRIES: option uses a len value that
> > >can be larger than the size of the structure itself.
> > >
> > >Then the data is copied to userspace in copy_entries_to_user() for ipv4
> > >and v6, and that's where the "bad data"
> > 
> > If the kernel incorrectly copies more bytes than it should, isn't that
> > a sign that may be going going past the end of the info buffer?
> > (And thus, zeroing won't truly fix the issue)
> 
> No, the buffer size is correct, we just aren't filling up the whole
> buffer as the data requested is smaller than the buffer size.

I have no objections to the patch but I'd like to understand what
problem its fixing.

Normal pattern is:
newinfo = xt_alloc_table_info(tmp.size);
copy_from_user(newinfo->entries, user + sizeof(tmp), tmp.size);

So inital value of the rule blob area should not matter.

Furthermore, when copying the rule blob back to userspace,
the kernel is not supposed to copy any padding back to userspace either,
since commit f32815d21d4d8287336fb9cef4d2d9e0866214c2 only the
user-relevant parts should be copied (some matches and targets allocate
kernel-private data such as pointers, and we did use to leak such pointer
values back to userspace).
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Greg Kroah-Hartman May 18, 2018, 11:04 a.m. | #6
On Fri, May 18, 2018 at 11:27:56AM +0200, Florian Westphal wrote:
> Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote:
> > On Thu, May 17, 2018 at 12:42:00PM +0200, Jan Engelhardt wrote:
> > > 
> > > On Thursday 2018-05-17 12:09, Greg Kroah-Hartman wrote:
> > > >> > --- a/net/netfilter/x_tables.c
> > > >> > +++ b/net/netfilter/x_tables.c
> > > >> > @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
> > > >> >  	 * than shoot all processes down before realizing there is nothing
> > > >> >  	 * more to reclaim.
> > > >> >  	 */
> > > >> > -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > > >> > +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > > >> >  	if (!info)
> > > >> >  		return NULL;
> > > >>
> > > >> I am curious, what particular path does not later overwrite the whole zone ?
> > > >
> > > >In do_ipt_get_ctl, the IPT_SO_GET_ENTRIES: option uses a len value that
> > > >can be larger than the size of the structure itself.
> > > >
> > > >Then the data is copied to userspace in copy_entries_to_user() for ipv4
> > > >and v6, and that's where the "bad data"
> > > 
> > > If the kernel incorrectly copies more bytes than it should, isn't that
> > > a sign that may be going going past the end of the info buffer?
> > > (And thus, zeroing won't truly fix the issue)
> > 
> > No, the buffer size is correct, we just aren't filling up the whole
> > buffer as the data requested is smaller than the buffer size.
> 
> I have no objections to the patch but I'd like to understand what
> problem its fixing.
> 
> Normal pattern is:
> newinfo = xt_alloc_table_info(tmp.size);
> copy_from_user(newinfo->entries, user + sizeof(tmp), tmp.size);
> 
> So inital value of the rule blob area should not matter.
> 
> Furthermore, when copying the rule blob back to userspace,
> the kernel is not supposed to copy any padding back to userspace either,
> since commit f32815d21d4d8287336fb9cef4d2d9e0866214c2 only the
> user-relevant parts should be copied (some matches and targets allocate
> kernel-private data such as pointers, and we did use to leak such pointer
> values back to userspace).

Ah, fun, commit f32815d21d4d ("xtables: add xt_match, xt_target and data
copy_to_user functions") showed up in 4.11 and this was reported in 4.4 :(

However, the "bad" code path seems to be from the IPT_SO_GET_ENTRIES
request, which does not look to use the new functions provided in
f32815d21d4d, or am I mistaken?

Let me go work on a reproducer for this to make it a lot more obvious
what is happening, and if it is still even an issue after f32815d21d4d
is applied to a kernel.  Sorry for not providing that in the first
place...

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Greg Kroah-Hartman May 26, 2018, 2:54 p.m. | #7
On Fri, May 18, 2018 at 11:27:56AM +0200, Florian Westphal wrote:
> Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote:
> > On Thu, May 17, 2018 at 12:42:00PM +0200, Jan Engelhardt wrote:
> > > 
> > > On Thursday 2018-05-17 12:09, Greg Kroah-Hartman wrote:
> > > >> > --- a/net/netfilter/x_tables.c
> > > >> > +++ b/net/netfilter/x_tables.c
> > > >> > @@ -1183,11 +1183,10 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
> > > >> >  	 * than shoot all processes down before realizing there is nothing
> > > >> >  	 * more to reclaim.
> > > >> >  	 */
> > > >> > -	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > > >> > +	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
> > > >> >  	if (!info)
> > > >> >  		return NULL;
> > > >>
> > > >> I am curious, what particular path does not later overwrite the whole zone ?
> > > >
> > > >In do_ipt_get_ctl, the IPT_SO_GET_ENTRIES: option uses a len value that
> > > >can be larger than the size of the structure itself.
> > > >
> > > >Then the data is copied to userspace in copy_entries_to_user() for ipv4
> > > >and v6, and that's where the "bad data"
> > > 
> > > If the kernel incorrectly copies more bytes than it should, isn't that
> > > a sign that may be going going past the end of the info buffer?
> > > (And thus, zeroing won't truly fix the issue)
> > 
> > No, the buffer size is correct, we just aren't filling up the whole
> > buffer as the data requested is smaller than the buffer size.
> 
> I have no objections to the patch but I'd like to understand what
> problem its fixing.
> 
> Normal pattern is:
> newinfo = xt_alloc_table_info(tmp.size);
> copy_from_user(newinfo->entries, user + sizeof(tmp), tmp.size);
> 
> So inital value of the rule blob area should not matter.
> 
> Furthermore, when copying the rule blob back to userspace,
> the kernel is not supposed to copy any padding back to userspace either,
> since commit f32815d21d4d8287336fb9cef4d2d9e0866214c2 only the
> user-relevant parts should be copied (some matches and targets allocate
> kernel-private data such as pointers, and we did use to leak such pointer
> values back to userspace).

Adding Peter to this thread, as he originally reported this issue to
Google back in February.

Peter, I know you reported this against the 4.4 kernel tree, but since
then, commit f32815d21d4d ("xtables: add xt_match, xt_target and data
copy_to_user functions") has been added to the kernel in release 4.11.
In digging through this crazy code path, I think the issue is still
there, but can not verify it for sure.

Is there any way you can run your tests on the 4.14 or newer kernel tree
to see if this issue really is fixed or not?

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
index cb7cb300c3bc..cd22bb9b66f3 100644
--- a/net/netfilter/x_tables.c
+++ b/net/netfilter/x_tables.c
@@ -1183,11 +1183,10 @@  struct xt_table_info *xt_alloc_table_info(unsigned int size)
 	 * than shoot all processes down before realizing there is nothing
 	 * more to reclaim.
 	 */
-	info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
+	info = kvzalloc(sz, GFP_KERNEL | __GFP_NORETRY);
 	if (!info)
 		return NULL;
 
-	memset(info, 0, sizeof(*info));
 	info->size = size;
 	return info;
 }