Skip to contentSkip to conversation

fs: select: fix information leak to userspace

PATCH In review Vasiliy Kulikov 14 participants 33 messages Nov 10, 2010 – Dec 16, 2010

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 fs: select: fix information leak to userspace

On some architectures __kernel_suseconds_t is int. On these archs struct timeval has padding bytes at the end. This struct is copied to userspace with these padding bytes uninitialized. This leads to leaking of contents of kernel stack memory.

This bug was added with v2.6.27-rc5-286-gb773ad4.

---
Compile tested.

 fs/select.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
fs/select.c +1 -0
index b7b10aa..32cf018 100644
@@ -306,6 +306,7 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
+		memset(&rtv, 0, sizeof(rtv));
 		rtv.tv_sec = rts.tv_sec;
 		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
 
Signature · 1 line
1.7.0.4
#1 lore raw
Quoted 1 line · On Wed, 10 Nov 2010 23:38:02 +0300 Vasiliy Kulikov <[email protected]> wrote:

On some architectures __kernel_suseconds_t is int.

On sparc and parisc. On all other architectures this patch is a waste of cycles.

Quoted 19 lines

On these archs
struct timeval has padding bytes at the end. This struct is copied to userspace with these padding bytes uninitialized. This leads to leaking of contents of kernel stack memory.

This bug was added with v2.6.27-rc5-286-gb773ad4.

---
Compile tested.

 fs/select.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
fs/select.c +0 -0
index b7b10aa..32cf018 100644

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 311

Quoted 6 lines
fs/select.c +1 -0
@@ -306,6 +306,7 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
+		memset(&rtv, 0, sizeof(rtv));
 		rtv.tv_sec = rts.tv_sec;
 		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;

How about this?

fs/select.c renamed from fs/select.c~fs-select-fix-information-leak-to-userspace-fix +2 -1
@@ -306,7 +306,8 @@ static int poll_select_copy_remaining(st
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
-		memset(&rtv, 0, sizeof(rtv));
+		if (sizeof(rtv) > sizeof(rtv.tv_sec) + sizeof(rtv.tv_usec))
+			memset(&rtv, 0, sizeof(rtv));
 		rtv.tv_sec = rts.tv_sec;
 		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
 

_

The `if' gets eliminated at compile time. With this approach we add four bytes of text to the sparc64 build and zero bytes of text to the x86_64 build.

#2 lore raw
Quoted 22 lines · Andrew Morton
Quoted 1 line · On Wed, 10 Nov 2010 23:38:02 +0300 Vasiliy Kulikov <[email protected]> wrote:

On some architectures __kernel_suseconds_t is int.

On sparc and parisc. On all other architectures this patch is a waste of cycles.

fs/select.c renamed from fs/select.c~fs-select-fix-information-leak-to-userspace-fix +2 -1
@@ -306,7 +306,8 @@ static int poll_select_copy_remaining(st
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
-		memset(&rtv, 0, sizeof(rtv));
+		if (sizeof(rtv) > sizeof(rtv.tv_sec) + sizeof(rtv.tv_usec))
+			memset(&rtv, 0, sizeof(rtv));
 		rtv.tv_sec = rts.tv_sec;
 		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
 

_

Quoted 3 lines

The `if' gets eliminated at compile time. With this approach we add four bytes of text to the sparc64 build and zero bytes of text to the x86_64 build.

It's nice to have comments (or at least a good commit message) for unusual code like this, so that in the future it is clear when this kind of workaround can be removed (e.g. if the time_t is changed to always be a 64-bit value for Y2038 issues, even on 32-bit arches).

Cheers, Andreas

#3 lore raw

PATCH v2 1/1 fs: select: fix information leak to userspace

On some architectures __kernel_suseconds_t is int. On these archs struct timeval has padding bytes at the end. This struct is copied to userspace with these padding bytes uninitialized. This leads to leaking of contents of kernel stack memory.

---
Patch v1 used memset(), it was waste of cycles on almost all archs.

Compile tested.

 fs/select.c |    7 ++++---
 1 files changed, 4 insertions(+), 3 deletions(-)
fs/select.c +4 -3
index b7b10aa..43d4805 100644
@@ -288,7 +288,6 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 				      int timeval, int ret)
 {
 	struct timespec rts;
-	struct timeval rtv;
 
 	if (!p)
 		return ret;
@@ -306,8 +305,10 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
 
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;
Signature · 1 line
1.7.0.4
#4 lore raw
Quoted 29 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:

On some architectures __kernel_suseconds_t is int. On these archs struct timeval has padding bytes at the end. This struct is copied to userspace with these padding bytes uninitialized. This leads to leaking of contents of kernel stack memory.

---
Patch v1 used memset(), it was waste of cycles on almost all archs.

Compile tested.

 fs/select.c |    7 ++++---
 1 files changed, 4 insertions(+), 3 deletions(-)
fs/select.c +0 -1
index b7b10aa..43d4805 100644
@@ -288,7 +288,6 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 				      int timeval, int ret)
 {
 	struct timespec rts;
-	struct timeval rtv;
 
 	if (!p)
 		return ret;
@@ -306,8 +305,10 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 		rts.tv_sec = rts.tv_nsec = 0;
 

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 10 lines
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
 
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

#5 lore raw
Quoted 10 lines · Andrew Morton
Quoted 9 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
Quoted 5 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 2 lines
fs/select.c +0 -0
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

Thats a good question.

In my understanding, gcc should initialize all holes (and other not mentioned fields) with 0, even for automatic storage [C99 only mandates this on static storage]

I tested on x86_64 and this is the case, but could not find a definitive answer in gcc documentation.

This kind of construct is widely used in networking tree.

Maybe we should ask to gcc experts if this behavior is guaranteed by gcc, or if we must review our code ;(

CC Jakub

Thanks !

#6 lore raw
Quoted 26 lines · Eric Dumazet
Quoted 10 lines · Andrew Morton
Quoted 9 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
Quoted 5 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 2 lines
fs/select.c +0 -0
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

Thats a good question.

In my understanding, gcc should initialize all holes (and other not mentioned fields) with 0, even for automatic storage [C99 only mandates this on static storage]

I tested on x86_64 and this is the case, but could not find a definitive answer in gcc documentation.

Quoted 9 lines

This kind of construct is widely used in networking tree.

Maybe we should ask to gcc experts if this behavior is guaranteed by gcc, or if we must review our code ;(

CC Jakub

Thanks !

This is what I thought too. If it is not there are tones of bugs I wrote of code that relays on this behaviour.

It would be interesting to know for sure

Thanks
Boaz

#7 lore raw
Quoted 34 lines · On Tue, 16 Nov 2010 13:19:36 +0200 Boaz Harrosh <[email protected]> wrote:
Quoted 33 lines · Eric Dumazet

Le dimanche 14 novembre 2010 __ 18:06 -0800, Andrew Morton a __crit :

Quoted 10 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:
Quoted 9 lines
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
Quoted 5 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 2 lines
fs/select.c +0 -0
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

Thats a good question.

In my understanding, gcc should initialize all holes (and other not mentioned fields) with 0, even for automatic storage [C99 only mandates this on static storage]

I tested on x86_64 and this is the case, but could not find a definitive answer in gcc documentation.

This kind of construct is widely used in networking tree.

Maybe we should ask to gcc experts if this behavior is guaranteed by gcc, or if we must review our code ;(

CC Jakub

Quoted 7 lines
Quoted 2 lines

Thanks !

This is what I thought too. If it is not there are tones of bugs I wrote of code that relays on this behaviour.

It would be interesting to know for sure

Well. We certainly assume in many places that

	struct foo {
		int a;
		int b;
	} f = {
		.a = 1,
	};

will initialise b to zero. But I doubt if much code at all assumes that this initialisation patterm will reliably zero out *holes* in the struct.

#8 lore raw
Quoted 9 lines · Andrew Morton

Well. We certainly assume in many places that

	struct foo {
		int a;
		int b;
	} f = {
		.a = 1,
	};
Quoted 4 lines

will initialise b to zero. But I doubt if much code at all assumes that this initialisation patterm will reliably zero out *holes* in the struct.

We did such assertions in the past, we were wrong.

Check commit 1c40be12f7d8ca1d387510d39787b12e512a7ce8 for an example (net sched: fix some kernel memory leaks)

I guess we must make a full audit of all C99 initializers or structures copied to userspace, giving a name to hidden holes, to force gcc to init them to 0.

# cat try.c
struct s {
	char c;
	long l;
	};

void bar(void *v)

{
	unsigned long *p = v;

	printf("%lx %lx\n", p[0], p[1]);
}

int main()

{
	struct s s1 = {
		.c = 1,
		.l = 2,
	};

	bar(&s1);
	return 0;
}
# gcc -O2 -o try try.c
# ./try
8049401 2

Strangely, if we remove ".l = 2," line, gcc emits code to clear al the fields

main:

	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$32, %esp
	leal	24(%esp), %eax
	movl	$0, 24(%esp)
	movl	%eax, (%esp)
	movl	$0, 28(%esp)
	movb	$1, 24(%esp)
	call	bar
	xorl	%eax, %eax
	leave
	ret
#9 lore raw
Quoted 49 lines · On Tue, 23 Nov 2010 01:20:48 +0100 Eric Dumazet <[email protected]> wrote:

Le lundi 22 novembre 2010 __ 15:50 -0800, Andrew Morton a __crit :

Quoted 9 lines

Well. We certainly assume in many places that

	struct foo {
		int a;
		int b;
	} f = {
		.a = 1,
	};
Quoted 4 lines

will initialise b to zero. But I doubt if much code at all assumes that this initialisation patterm will reliably zero out *holes* in the struct.

We did such assertions in the past, we were wrong.

Check commit 1c40be12f7d8ca1d387510d39787b12e512a7ce8 for an example (net sched: fix some kernel memory leaks)

I guess we must make a full audit of all C99 initializers or structures copied to userspace, giving a name to hidden holes, to force gcc to init them to 0.

# cat try.c
struct s {
	char c;
	long l;
	};

void bar(void *v)

{
	unsigned long *p = v;

	printf("%lx %lx\n", p[0], p[1]);
}

int main()

{
	struct s s1 = {
		.c = 1,
		.l = 2,
	};

	bar(&s1);
	return 0;
}
Quoted 3 lines
# gcc -O2 -o try try.c
# ./try
8049401 2

OK, thanks. That rather settles it then. memset() it is.

Quoted 2 lines

Strangely, if we remove ".l = 2," line, gcc emits code to clear al the fields

Maybe a glitch, maybe a small optimisation? That's the sort of thing which will change over gcc versions too..

#10 lore raw
Quoted 14 lines · Andrew Morton
Quoted 11 lines · On Tue, 23 Nov 2010 01:20:48 +0100 Eric Dumazet <[email protected]> wrote:

Le lundi 22 novembre 2010 __ 15:50 -0800, Andrew Morton a __crit :

Quoted 9 lines

Well. We certainly assume in many places that

	struct foo {
		int a;
		int b;
	} f = {
		.a = 1,
	};
Quoted 6 lines
Quoted 6 lines
Quoted 4 lines

will initialise b to zero. But I doubt if much code at all assumes that this initialisation patterm will reliably zero out *holes* in the struct.

We did such assertions in the past, we were wrong.

Well, that sucks... I know I wrote some code that relied on holes getting zeroed as well. Is there no option to GCC to make this work?

regards,
dan carpenter

#11 lore raw
Quoted 3 lines · Dan Carpenter

Well, that sucks... I know I wrote some code that relied on holes getting zeroed as well. Is there no option to GCC to make this work?

Apparently not.

At least, commits 0f04cfd098fb81fded74e78ea1a1b86cc6c6c31e and 1c40be12f7d8ca1d387510d39787b12e512a7ce8 were safe, as structures that were touched dont have holes.

#12 lore raw
In reply to Eric Dumazet
Quoted 17 lines · Eric Dumazet
Quoted 10 lines · Andrew Morton
Quoted 9 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
Quoted 5 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 2 lines
fs/select.c +0 -0
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

Quoted 9 lines

Thats a good question.

In my understanding, gcc should initialize all holes (and other not mentioned fields) with 0, even for automatic storage [C99 only mandates this on static storage]

I tested on x86_64 and this is the case, but could not find a definitive answer in gcc documentation.

Yeah, this is not clearly defined by C99 I think, but we can still find some clues in 6.2.6.1, Paragraph 6,

"
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. "

So we can't rely on the compiler to initialize the padding bytes too.

Signature · 6 lines
Live like a child, think like the god.
 
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#13 lore raw

Am 23.11.2010 15:01, schrieb Américo Wang:

Quoted 31 lines · Eric Dumazet
Quoted 17 lines · Andrew Morton
Quoted 10 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:
Quoted 9 lines
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
Quoted 5 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 2 lines
fs/select.c +0 -0
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

Quoted 9 lines

Thats a good question.

In my understanding, gcc should initialize all holes (and other not mentioned fields) with 0, even for automatic storage [C99 only mandates this on static storage]

I tested on x86_64 and this is the case, but could not find a definitive answer in gcc documentation.

Yeah, this is not clearly defined by C99 I think, but we can still find some clues in 6.2.6.1, Paragraph 6,

Quoted 9 lines

"
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. "

So we can't rely on the compiler to initialize the padding bytes too.

hi all,
as we see this is not a question of c99.
Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

People that still want a "undefined" (for what ever reason) can use an option to enable it again (e.g. --no-zero-padding).

do anyone have a contact so we can forward that request ?

re,
wh

#14 lore raw
Quoted 23 lines · walter harms

Am 23.11.2010 15:01, schrieb Américo Wang:

Quoted 13 lines · Eric Dumazet
Quoted 8 lines

In my understanding, gcc should initialize all holes (and other not mentioned fields) with 0, even for automatic storage [C99 only mandates this on static storage]

I tested on x86_64 and this is the case, but could not find a definitive answer in gcc documentation.

Yeah, this is not clearly defined by C99 I think, but we can still find some clues in 6.2.6.1, Paragraph 6,

Quoted 9 lines

"
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. "

So we can't rely on the compiler to initialize the padding bytes too.

Quoted 8 lines

hi all,
as we see this is not a question of c99.
Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

People that still want a "undefined" (for what ever reason) can use an option to enable it again (e.g. --no-zero-padding).

Well, IMHO, the default behavior should be "undefined", thus "-fzero-padding" is needed. But, you know, I am not a compiler people at all. :)

Quoted 3 lines

do anyone have a contact so we can forward that request ?

[email protected] ?

Signature · 6 lines
Live like a child, think like the god.
 
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#15 lore raw
In reply to walter harms
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures. Since GCC is already explicitly zeroing the _used_ fields in the struct, it can much more easily determine whether there is padding in the structure, and zero those few bytes as needed.

Cheers, Andreas

#16 lore raw
Quoted 4 lines · On Tue, 23 Nov 2010 11:02:44 -0700 Andreas Dilger <[email protected]>…
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures.

(My, what long lines you have!)

We can't reasonably address this with gcc changes. If gcc starts doing what we want in the next release, how long will it be until that release is the *oldest* version of gcc which the kernel may be compiled with? Five years?

#17 lore raw

From: Andrew Morton <[email protected]>
Date: Tue, 23 Nov 2010 12:18:40 -0800

Quoted 4 lines

We can't reasonably address this with gcc changes. If gcc starts doing what we want in the next release, how long will it be until that release is the *oldest* version of gcc which the kernel may be compiled with? Five years?

Completely agreed.

#18 lore raw
In reply to Andrew Morton
Quoted 4 lines · Andrew Morton
Quoted 1 line · On Tue, 23 Nov 2010 11:02:44 -0700 Andreas Dilger <[email protected]>…

On 2010-11-23, at 07:45, walter harms wrote:

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures.

We can't reasonably address this with gcc changes. If gcc starts doing what we want in the next release, how long will it be until that release is the *oldest* version of gcc which the kernel may be compiled with? Five years?

At the same time, we've had these same issues for 15+ years already, and IMHO I don't think anyone can extract a great deal of "information" from 4 bytes of random data interspersed in the middle of some structure that is otherwise initialized or zeroed out by the compiler.

On the other hand, calling memset() on these structures imposes a (virtually) permanent overhead on all processing of those structures.

If people are genuinely concerned about this kind of "information leak", a far better solution would be to add explicit padding fields to the structures (that are otherwise never going to be used) and then GCC will automatically initialize them without the overhead of initializing the fields twice.

Cheers, Andreas

#19 lore raw
In reply to Andrew Morton

Am 23.11.2010 21:18, schrieb Andrew Morton:

Quoted 6 lines
Quoted 3 lines · On Tue, 23 Nov 2010 11:02:44 -0700 Andreas Dilger <[email protected]>…
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

Quoted 10 lines
Quoted 1 line

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures.

(My, what long lines you have!)

We can't reasonably address this with gcc changes. If gcc starts doing what we want in the next release, how long will it be until that release is the *oldest* version of gcc which the kernel may be compiled with? Five years?

agreed, but it does not mean not to talk to the gcc people that there is an interesse to change.

re,
wh

#20 lore raw
In reply to Andreas Dilger
Quoted 4 lines · Andreas Dilger
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures. Since GCC is already explicitly zeroing the _used_ fields in the struct, it can much more easily determine whether there is padding in the structure, and zero those few bytes as needed.

Zero padding structs is part of C90. Details here:
http://www.pixelbeat.org/programming/gcc/auto_init.html

gcc doesn't zero pad when _all_ elements are specified.

So perhaps just:

-        struct timespec rts;
-        struct timeval rtv;
+        struct timespec rts = {0,};
+        struct timeval rtv = {0,};

One could also move the rtv declaration
to the scope where it's used.

cheers,
Pádraig.
--
To unsubscribe from this list: send the line "unsubscribe kernel-janitors" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#21 lore raw
Quoted 8 lines · Pádraig Brady
Quoted 4 lines · Andreas Dilger
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures. Since GCC is already explicitly zeroing the _used_ fields in the struct, it can much more easily determine whether there is padding in the structure, and zero those few bytes as needed.

Zero padding structs is part of C90. Details here:
http://www.pixelbeat.org/programming/gcc/auto_init.html

Nope.

Quoted 3 lines

gcc doesn't zero pad when _all_ elements are specified.

That is what gcc does, not what C standard specifies.

#22 lore raw
Quoted 10 lines · Américo Wang
Quoted 8 lines · Pádraig Brady
Quoted 4 lines · Andreas Dilger
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures. Since GCC is already explicitly zeroing the _used_ fields in the struct, it can much more easily determine whether there is padding in the structure, and zero those few bytes as needed.

Zero padding structs is part of C90. Details here:
http://www.pixelbeat.org/programming/gcc/auto_init.html

Quoted 7 lines

Nope.

Quoted 3 lines

gcc doesn't zero pad when _all_ elements are specified.

That is what gcc does, not what C standard specifies.

Looks like gcc is following the standard exactly.

C90 - 6.5.7
C99 - 6.7.8

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate ... the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

cheers,
Pádraig.

#23 lore raw
Quoted 21 lines · Pádraig Brady
Quoted 10 lines · Américo Wang
Quoted 8 lines · Pádraig Brady
Quoted 4 lines · Andreas Dilger
Quoted 1 line · walter harms

Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

It makes the most sense to tackle this at the GCC level, since the added overhead of doing memset(0) on the whole struct may be non-trivial for commonly-used and/or large structures. Since GCC is already explicitly zeroing the _used_ fields in the struct, it can much more easily determine whether there is padding in the structure, and zero those few bytes as needed.

Zero padding structs is part of C90. Details here:
http://www.pixelbeat.org/programming/gcc/auto_init.html

Quoted 7 lines

Nope.

Quoted 3 lines

gcc doesn't zero pad when _all_ elements are specified.

That is what gcc does, not what C standard specifies.

Looks like gcc is following the standard exactly.

Quoted 8 lines

C90 - 6.5.7
C99 - 6.7.8

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate ... the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

Depends on if "the remainder of the aggregate" includes padding bytes or not.

maintainer
#24 lore raw
In reply to Pádraig Brady
Quoted 9 lines · P??draig Brady

Looks like gcc is following the standard exactly.

C90 - 6.5.7
C99 - 6.7.8

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate ... the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

Incorrect. See 6.2.6.1 in C99; basically, padding bytes have unspecified contents. Implementation is allowed to leave them in any state (including not bothering to copy them when doing struct assignments, etc.). See Appendix J (portability issues) as well.

The text you are quoting is about handling of missing initializers - it essentially refers you back to the rules in 6.7.8p{9,10} (for arithmetical types use zero, for pointers - null pointer, for arrays - apply recursively to each array member, for structs - apply recursively for each named member, for unions - apply recursively for the first named member). That's it - nothing in the standard says how to initialize padding. Note that these rules are _not_ guaranteed to be "fill everything with zero bits"; it often will be true, but it's implementation-dependent. Implementation may decide to fill padding with all-zeroes; it's not required to do so.

The bottom line: if you rely on that, you are relying on non-portable details of compiler behaviour. Moreover, the authors are not even required to document what they are doing or to keep that behaviour consistent.

#25 lore raw
Quoted 9 lines · Al Viro

Incorrect. See 6.2.6.1 in C99; basically, padding bytes have unspecified contents. Implementation is allowed to leave them in any state (including not bothering to copy them when doing struct assignments, etc.). See Appendix J (portability issues) as well.

The bottom line: if you rely on that, you are relying on non-portable details of compiler behaviour. Moreover, the authors are not even required to document what they are doing or to keep that behaviour consistent.

I thought my proposed solution was reasonable - add explicit padding fields where there are holes in the structure, which would be unused by the kernel, but since they are defined fields the compiler is obligated to initialize them.

This wouldn't add any overhead in cases where the compiler is already initializing the fields, and is still going to be less overhead than doing a memset(0) on the whole structure, and then initializing the other fields explicitly.

That has the added bonus that it becomes instantly clear where there are padding fields in the structure, and possibly they can be put to use in the future.

Cheers, Andreas

#26 lore raw
Quoted 6 lines · Andreas Dilger
Quoted 5 lines · Al Viro

Incorrect. See 6.2.6.1 in C99; basically, padding bytes have unspecified contents. Implementation is allowed to leave them in any state (including not bothering to copy them when doing struct assignments, etc.). See Appendix J (portability issues) as well.

Quoted 6 lines
Quoted 4 lines

The bottom line: if you rely on that, you are relying on non-portable details of compiler behaviour. Moreover, the authors are not even required to document what they are doing or to keep that behaviour consistent.

I thought my proposed solution was reasonable - add explicit padding fields where there are holes in the structure, which would be unused by the kernel, but since they are defined fields the compiler is obligated to initialize them.

Is the presence of holes always apparent at the source code level? Or is it dependent on the compiler or target architecture?

julia

Quoted 15 lines

This wouldn't add any overhead in cases where the compiler is already initializing the fields, and is still going to be less overhead than doing a memset(0) on the whole structure, and then initializing the other fields explicitly.

That has the added bonus that it becomes instantly clear where there are padding fields in the structure, and possibly they can be put to use in the future.

Cheers, Andreas

--
To unsubscribe from this list: send the line "unsubscribe kernel-janitors" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#27 lore raw
Quoted 13 lines · Julia Lawall
Quoted 11 lines · Andreas Dilger
Quoted 9 lines · Al Viro

Incorrect. See 6.2.6.1 in C99; basically, padding bytes have unspecified contents. Implementation is allowed to leave them in any state (including not bothering to copy them when doing struct assignments, etc.). See Appendix J (portability issues) as well.

The bottom line: if you rely on that, you are relying on non-portable details of compiler behaviour. Moreover, the authors are not even required to document what they are doing or to keep that behaviour consistent.

Quoted 4 lines
Quoted 1 line

I thought my proposed solution was reasonable - add explicit padding fields where there are holes in the structure, which would be unused by the kernel, but since they are defined fields the compiler is obligated to initialize them.

Is the presence of holes always apparent at the source code level? Or is it dependent on the compiler or target architecture?

It depends on target architecture.

This means doing a full review to add a named padding only for arches that need it.

#28 lore raw
Quoted 11 lines · Eric Dumazet
Quoted 5 lines · Julia Lawall
Quoted 1 line · Andreas Dilger

I thought my proposed solution was reasonable - add explicit padding fields where there are holes in the structure, which would be unused by the kernel, but since they are defined fields the compiler is obligated to initialize them.

Is the presence of holes always apparent at the source code level? Or is it dependent on the compiler or target architecture?

It depends on target architecture.

This means doing a full review to add a named padding only for arches that need it.

There are automated tools like "pahole" (IIRC) that will report the presence of these structure holes. However, the memset(0) won't add itself to the code either (i.e. it needs an audit to determine if it is needed).

Cheers, Andreas

--
To unsubscribe from this list: send the line "unsubscribe kernel-janitors" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#29 lore raw
Quoted 8 lines · Andreas Dilger
Quoted 7 lines · Eric Dumazet
Quoted 5 lines · Julia Lawall
Quoted 1 line · Andreas Dilger

I thought my proposed solution was reasonable - add explicit padding fields where there are holes in the structure, which would be unused by the kernel, but since they are defined fields the compiler is obligated to initialize them.

Is the presence of holes always apparent at the source code level? Or is it dependent on the compiler or target architecture?

If you let the compiler have the driving sit. But if you take matter to your own hand, and one should when dealing with external interfaces that might get accessed from different compilers/languages.

so:

struct export_foo {
	u32 m1;
	u32 padding1;
	u64 m2
	....
} __attribute__((aligned(8),packed))

will make sure that even other compilers/versions will do the same thing. Also the same rule for on the wire structures.

Just choose the biggest type you have in the structure and specify that, which will make good code for most cases.

At the end this things depend on if sizeof(long) is 8 or 4

Quoted 9 lines
Quoted 4 lines

It depends on target architecture.

This means doing a full review to add a named padding only for arches that need it.

There are automated tools like "pahole" (IIRC) that will report the presence of these structure holes. However, the memset(0) won't add itself to the code either (i.e. it needs an audit to determine if it is needed).

I agree. It is always good practice for public structures to be considered more delicately. All padding should be spelled out. Not only for security but for future compatibility and maintenance.

Quoted 3 lines

Cheers, Andreas

Thanks
Boaz

#30 lore raw
In reply to walter harms
Quoted 5 lines · walter harms

hi all,
as we see this is not a question of c99.
Maybe we can convince the gcc people to make 0 padding default. That will not solve the problems for other compilers but when they claim "works like gcc" we can press then to support this also. I can imagine that this will close some other subtle leaks also.

Note that zero padding by default has a price - the code has to include zeroing instructions for each structure that needs it. In the case of a function that gets inlined, the zeroing instructions could easily cost almost as much as the actual function. So your code ends up bigger and slower. Let's look at that example again:

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 10 lines
fs/select.c +4 -2
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
 
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Quick - is the optimizer able to eliminate the zero padding? If so, how does it know that? And if the optimizer *can't* eliminate the zero padding, what does that to do the overall generated code quality (especially on CPUs like the x86 in 32-bit mode, where there's significant register pressure)?

You might be able to get them to add an *option* to force zero-padding, but there's no way that's going to become the default.

#31 lore raw
In reply to Andrew Morton
Quoted 39 lines · Andrew Morton
Quoted 37 lines · On Sun, 14 Nov 2010 12:25:33 +0300 Vasiliy Kulikov <[email protected]> wrote:

On some architectures __kernel_suseconds_t is int. On these archs struct timeval has padding bytes at the end. This struct is copied to userspace with these padding bytes uninitialized. This leads to leaking of contents of kernel stack memory.

---
Patch v1 used memset(), it was waste of cycles on almost all archs.

Compile tested.

 fs/select.c |    7 ++++---
 1 files changed, 4 insertions(+), 3 deletions(-)
fs/select.c +4 -3
index b7b10aa..43d4805 100644
@@ -288,7 +288,6 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 				      int timeval, int ret)
 {
 	struct timespec rts;
-	struct timeval rtv;
 
 	if (!p)
 		return ret;
@@ -306,8 +305,10 @@ static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
-		rtv.tv_sec = rts.tv_sec;
-		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
+		struct timeval rtv = {
+			.tv_sec = rts.tv_sec,
+			.tv_usec = rts.tv_nsec / NSEC_PER_USEC
+		};
 
Quoted 5 lines

fs/select.c static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, · line 314

Quoted 2 lines
fs/select.c +0 -0
 		if (!copy_to_user(p, &rtv, sizeof(rtv)))
 			return ret;

Please check the assembly code - this will still leave four bytes of uninitalised stack data in 'rtv', surely.

This concrete c code generates movl + movq, movl would zero unnamed 4 bytes. However, I cannot find whether this behavior is guaranteed...

Signature · 1 line
Vasiliy Kulikov
#32 lore raw
In reply to Andreas Dilger
Quoted 20 lines · On Sat, 13 Nov 2010 14:38:19 -0700 Andreas Dilger <[email protected]>…
Quoted 19 lines · Andrew Morton
Quoted 1 line · On Wed, 10 Nov 2010 23:38:02 +0300 Vasiliy Kulikov <[email protected]> wrote:

On some architectures __kernel_suseconds_t is int.

On sparc and parisc. On all other architectures this patch is a waste of cycles.

fs/select.c renamed from fs/select.c~fs-select-fix-information-leak-to-userspace-fix +2 -1
@@ -306,7 +306,8 @@ static int poll_select_copy_remaining(st
 		rts.tv_sec = rts.tv_nsec = 0;
 
 	if (timeval) {
-		memset(&rtv, 0, sizeof(rtv));
+		if (sizeof(rtv) > sizeof(rtv.tv_sec) + sizeof(rtv.tv_usec))
+			memset(&rtv, 0, sizeof(rtv));
 		rtv.tv_sec = rts.tv_sec;
 		rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC;
 
Quoted 9 lines
Quoted 6 lines

_

The `if' gets eliminated at compile time. With this approach we add four bytes of text to the sparc64 build and zero bytes of text to the x86_64 build.

It's nice to have comments (or at least a good commit message) for unusual code like this, so that in the future it is clear when this kind of workaround can be removed (e.g. if the time_t is changed to always be a 64-bit value for Y2038 issues, even on 32-bit arches).

Well, I'm the resident comment fanatic, but I thought this was all sufficiently obvious to not need one. But I'll add one ;)