kernel-hardening.lists.openwall.com archive mirror
 help / color / mirror / Atom feed
* two potential randstruct improvements
@ 2021-03-29  7:26 Jann Horn
  2021-03-29 10:16 ` Vegard Nossum
  2021-03-30 21:18 ` Kees Cook
  0 siblings, 2 replies; 3+ messages in thread
From: Jann Horn @ 2021-03-29  7:26 UTC (permalink / raw)
  To: Kernel Hardening, Kees Cook, Brad Spengler, PaX Team

Hi!

I'm currently in the middle of writing a blogpost on some Linux kernel
stuff; while working on that I looked at the randstruct version in
Linus' tree a bit and noticed two potential areas for improvement. I
have no idea whether any of this (still) applies to the PaX/grsecurity
version, but since the code originates from there, I figured I should
also send this to the original authors.


=== no explicit randomization of padding holes ===
The randstruct plugin works by shuffling the list of C struct members.
So if you have a struct like this:

struct foo { u32 a; /*4-byte hole*/ u64 b; u64 c; };

randstruct might rearrange it into one of the following layouts:

struct foo { u32 a; /*4-byte hole*/ u64 b; u64 c; };
struct foo { u32 a; /*4-byte hole*/ u64 c; u64 b; };
struct foo { u64 b; u32 a; /*4-byte hole*/ u64 c; };
struct foo { u64 b; u64 c; u32 a; /*4-byte hole*/ };
struct foo { u64 c; u32 a; /*4-byte hole*/ u64 b; };
struct foo { u64 c; u64 b; u32 a; /*4-byte hole*/ };

So if there is only a single 4-byte member among multiple 8-byte
members, the 4-byte member "a" will still always be 8-byte aligned;
and if there is a small number of 4-byte members among lots of 8-byte
members, it'll probably still end up that way. This means that if an
attacker e.g. manages to type-confuse "struct foo" and an array of
pointers on a little-endian system, they'll be able to use arithmetic
operations on "a" to shift one of the pointers in "a" up and down.
This wouldn't be possible if, after the existing randomization, struct
members with following padding holes were explicitly randomized with
regard to the padding (subject to alignment constraints, of course).
(In practice I guess that might be implemented in the existing
randstruct plugin by computing padding holes after elements and then
randomly inserting dummy members in front of those members, with
dummy_size%member_alignment==0 and dummy_size<=padding_size.)

(Yes, I realize that this becomes less interesting if you have a
different mitigation that makes type confusion between single-struct
allocations and arrays harder.)


=== non-cryptographic RNG used for randomization ===
I haven't looked at this in detail; but randstruct uses a
non-cryptographic RNG
(http://burtleburtle.net/bob/rand/smallprng.html) to derive randomized
structs from a 256-bit seed. In theory, this means that an attacker
with knowledge of at least 256 bits worth of information about
structure layouts in a given build _may_ be able to recover the seed,
and from there, the layouts of all other structs.

It might be possible to indirectly determine some amount of
information on structure layouts through various side channels; for
example:

 - cacheline grouping might change if performance-mode is disabled,
   which might be measurable through false sharing effects
 - function sizes might change slightly because the encoding of an access
   to the first element is shorter, which might be measurable e.g. through
   icache and branch predictor state

I don't know whether the amount of information leakage would be enough
to actually determine the seed - and I'm not a cryptographer, I have
no clue how much output from the RNG you'd actually need to recover
the seed (and an attacker would not even be getting raw RNG output,
but RNG output after additional modulo operations). But if the goal
here is to ensure that an attacker without access to the binary kernel
image can't determine struct layouts without a proper leak primitive,
even if they know exactly from which sources and with what
configuration the kernel was built, then I think this needs a
cryptographically secure RNG.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: two potential randstruct improvements
  2021-03-29  7:26 two potential randstruct improvements Jann Horn
@ 2021-03-29 10:16 ` Vegard Nossum
  2021-03-30 21:18 ` Kees Cook
  1 sibling, 0 replies; 3+ messages in thread
From: Vegard Nossum @ 2021-03-29 10:16 UTC (permalink / raw)
  To: Jann Horn, Kernel Hardening, Kees Cook, Brad Spengler, PaX Team


On 2021-03-29 09:26, Jann Horn wrote:
> Hi!
> 
> I'm currently in the middle of writing a blogpost on some Linux kernel
> stuff; while working on that I looked at the randstruct version in
> Linus' tree a bit and noticed two potential areas for improvement. I
> have no idea whether any of this (still) applies to the PaX/grsecurity
> version, but since the code originates from there, I figured I should
> also send this to the original authors.
> 
> 

[...]

> I don't know whether the amount of information leakage would be enough
> to actually determine the seed - and I'm not a cryptographer, I have
> no clue how much output from the RNG you'd actually need to recover
> the seed (and an attacker would not even be getting raw RNG output,
> but RNG output after additional modulo operations). But if the goal
> here is to ensure that an attacker without access to the binary kernel
> image can't determine struct layouts without a proper leak primitive,
> even if they know exactly from which sources and with what
> configuration the kernel was built, then I think this needs a
> cryptographically secure RNG.


Hi,

I just wanted to add something that stood out to me (assuming I'm 
reading the code correctly):

It looks like the per-struct seed is constructed only based on a hash of 
the struct name (using name_hash()), and anonymous structs use the name 
"anonymous", which means that anonymous structs with the same number of 
members will always be shuffled the same way (using full_shuffle() at 
least). Which means that you can gain information about one struct and 
potentially use it on another. It doesn't look like anonymous structs 
being randomized is very common, a quick run against kernel/fork.c shows 
there's only 3 cases and they all have different numbers of members (7, 
59, and 182). In any case, to mitigate this, maybe include some details 
of the struct (original member offsets/sizes/names) in the per-struct 
seed derivation?

I definitely second the recommendation to use cryptographically secure 
algorithms -- specifically, use a 256-bit HMAC that depends on the seed 
instead of name_hash() and a cryptographically secure PRNG for ranval().


Vegard

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: two potential randstruct improvements
  2021-03-29  7:26 two potential randstruct improvements Jann Horn
  2021-03-29 10:16 ` Vegard Nossum
@ 2021-03-30 21:18 ` Kees Cook
  1 sibling, 0 replies; 3+ messages in thread
From: Kees Cook @ 2021-03-30 21:18 UTC (permalink / raw)
  To: Jann Horn; +Cc: Kernel Hardening, Brad Spengler, PaX Team, linux-hardening

On Mon, Mar 29, 2021 at 09:26:35AM +0200, Jann Horn wrote:
> Hi!
> 
> I'm currently in the middle of writing a blogpost on some Linux kernel
> stuff; while working on that I looked at the randstruct version in
> Linus' tree a bit and noticed two potential areas for improvement. I
> have no idea whether any of this (still) applies to the PaX/grsecurity
> version, but since the code originates from there, I figured I should
> also send this to the original authors.
> 
> 
> === no explicit randomization of padding holes ===
> The randstruct plugin works by shuffling the list of C struct members.
> So if you have a struct like this:
> 
> struct foo { u32 a; /*4-byte hole*/ u64 b; u64 c; };
> 
> randstruct might rearrange it into one of the following layouts:
> 
> struct foo { u32 a; /*4-byte hole*/ u64 b; u64 c; };
> struct foo { u32 a; /*4-byte hole*/ u64 c; u64 b; };
> struct foo { u64 b; u32 a; /*4-byte hole*/ u64 c; };
> struct foo { u64 b; u64 c; u32 a; /*4-byte hole*/ };
> struct foo { u64 c; u32 a; /*4-byte hole*/ u64 b; };
> struct foo { u64 c; u64 b; u32 a; /*4-byte hole*/ };
> 
> So if there is only a single 4-byte member among multiple 8-byte
> members, the 4-byte member "a" will still always be 8-byte aligned;
> and if there is a small number of 4-byte members among lots of 8-byte
> members, it'll probably still end up that way. This means that if an
> attacker e.g. manages to type-confuse "struct foo" and an array of
> pointers on a little-endian system, they'll be able to use arithmetic
> operations on "a" to shift one of the pointers in "a" up and down.
> This wouldn't be possible if, after the existing randomization, struct
> members with following padding holes were explicitly randomized with
> regard to the padding (subject to alignment constraints, of course).
> (In practice I guess that might be implemented in the existing
> randstruct plugin by computing padding holes after elements and then
> randomly inserting dummy members in front of those members, with
> dummy_size%member_alignment==0 and dummy_size<=padding_size.)
> 
> (Yes, I realize that this becomes less interesting if you have a
> different mitigation that makes type confusion between single-struct
> allocations and arrays harder.)

Yup, it would be a nice improvement. It would need some work to reorganize
the shuffler, which doesn't have a way to insert fields currently. It
can sort of calculate padding (see partition_struct()), but that likely
needs improvement too.

Patches welcome! I've opened an issue for this:
https://github.com/KSPP/linux/issues/122

> === non-cryptographic RNG used for randomization ===
> I haven't looked at this in detail; but randstruct uses a
> non-cryptographic RNG
> (http://burtleburtle.net/bob/rand/smallprng.html) to derive randomized
> structs from a 256-bit seed. In theory, this means that an attacker
> with knowledge of at least 256 bits worth of information about
> structure layouts in a given build _may_ be able to recover the seed,
> and from there, the layouts of all other structs.
> 
> It might be possible to indirectly determine some amount of
> information on structure layouts through various side channels; for
> example:
> 
>  - cacheline grouping might change if performance-mode is disabled,
>    which might be measurable through false sharing effects
>  - function sizes might change slightly because the encoding of an access
>    to the first element is shorter, which might be measurable e.g. through
>    icache and branch predictor state
> 
> I don't know whether the amount of information leakage would be enough
> to actually determine the seed - and I'm not a cryptographer, I have
> no clue how much output from the RNG you'd actually need to recover
> the seed (and an attacker would not even be getting raw RNG output,
> but RNG output after additional modulo operations). But if the goal
> here is to ensure that an attacker without access to the binary kernel
> image can't determine struct layouts without a proper leak primitive,
> even if they know exactly from which sources and with what
> configuration the kernel was built, then I think this needs a
> cryptographically secure RNG.

Since the RNG runs on every compilation unit on every randomized
structure, any performance hit from swapping the RNG could be large.
That said, folks using randstruct likely don't care. :)

This looks easier to do than the former idea, though. Again, patches
welcome! Issue captured at: https://github.com/KSPP/linux/issues/123

-Kees

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-03-30 21:18 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-29  7:26 two potential randstruct improvements Jann Horn
2021-03-29 10:16 ` Vegard Nossum
2021-03-30 21:18 ` Kees Cook

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).