Making C Less Dangerous in the Linux Kernel
LINUX.CONF.AU 21-25 January 2019 Christchurch, NZ The Linux of Things | #LCA2019 | @linuxconfau
Kees Cook | @keescook
Making C Less Dangerous in the Linux Kernel Kees Cook | @keescook - - PowerPoint PPT Presentation
Making C Less Dangerous in the Linux Kernel Kees Cook | @keescook LINUX.CONF.AU 21-25 January The Linux of Things | #LCA2019 | @linuxconfau 2019 Christchurch, NZ Making C Less Dangerous in the Linux Kernel Linux Conf AU January 25,
LINUX.CONF.AU 21-25 January 2019 Christchurch, NZ The Linux of Things | #LCA2019 | @linuxconfau
Kees Cook | @keescook
– Kernel Self Protection Project – C as a fancy assembler
– Variable Length Arrays are bad and slow – Explicit switch case fall-through – Always-initialized automatic variables – Arithmetic overflow detection – Hope for bounds checking – Control Flow Integrity: forward edges – Control Flow Integrity: backward edges – Where are we now? – How you can help
@Rob_Russell
– What are the contents of “uninitialized” variables?
– v
d pointers have no type yet we can call typed functions through them?
– Why does m
e m c p y ( ) have no “max destination length” argument?
– https://raphlinus.github.io/programming/rust/2018/08/17/undefined-behavior.html
W v l a
s t a c k
l a s h
r
e c t i
stack 1 … … ... stack 2 … … ... size = 8192; ... char buf[size]; … strcpy(buf, src, size); stack 1 … … ... stack 2 … … ... guard page size = 8192; ... u8 array[size]; … array[big] = foo;
fixed-size array variable length array
Did they mean to leave
r e a k ; ” ??
– Actually a comment, but is parsed by compilers now, following the
–
S T R U C T L E A K (for structs with _ _ u s e r pointers)
–
S T R U C T L E A K _ B Y R E F (when passed into funcs)
–
Soon, plugin to mimic
i n i t
a l
a r s too
– Only signed. Fast: in the noise. Big: warnings grow kernel image by
– c
– s
– m
– s
– s
– m
t r c p y ( ) no bounds checking on destination nor source!
t r n c p y ( ) doesn’t always NUL terminate (good for non-C-strings, does NUL pad destination)
c h a r d e s t [ 4 ] ; s t r n c p y ( d e s t , “
a i ! ” , s i z e
( d e s t ) ) ; / * u n h e l p f u l l y r e t u r n s d e s t * / d e s t : “
, “ h ” , “ a ” , “ i ” … no trailing NUL byte :(
t r l c p y ( ) reads source beyond max destination size (returns length of source!)
t r s c p y ( ) safest (returns bytes copied, not including NUL, or -E2BIG)
s s i z e _ t c
n t = s t r s c p y ( d e s t , “
a i ! ” , s i z e
( d e s t ) ) ; / * r e t u r n s
2 B I G * / d e s t : “
, “ h ” , “ a ” , N U L
–
Does not NUL-pad destination … if desired, add explicit m e m s e t ( ) (kernel needs a helper for this...) i f ( c
n t > & & c
n t + 1 < s i z e
( d e s t ) ) m e m s e t ( d e s t + c
n t + 1 , , s i z e
( d e s t ) – c
n t
) ;
i n t c
n t = s n p r i n t f ( b u f , s i z e
( b u f ) , f m t … , … ) ; f
( i = ; i < s
e t h i n g ; i + + ) c
n t + = s n p r i n t f ( b u f + c
n t , s i z e
( b u f )
n t , f m t … , … ) ; c
y _ t
u s e r ( u s e r , b u f , c
n t ) ;
–
Replace in above code!
e m c p y ( ) has no sense of destination size :(
u i n t 8 _ t b y t e s [ 1 2 8 ] ; s i z e _ t w a n t e d , c
i e d = ; f
( i = ; i < s
e t h i n g & & c
i e d < s i z e
( b y t e s ) ; i + + ) { w a n t e d = . . . ; i f ( w a n t e d > s i z e
( b y t e s )
i e d ) w a n t e d = s i z e
( b y t e s )
i e d ; m e m c p y ( b y t e s + c
i e d , w a n t e d , s
r c e ) ; c
i e d + = w a n t e d ; }
– SPARC Application Data Integrity (ADI) – ARMv8.5 Memory Tagging Extension (MTE) – Intel?
@0x…….beef0000: first byte of 128 byte alloc … data ... @0x…….beef0040: … data ... @0x…….beef007ff: last byte of tagged region char *buf; buf = kmalloc(128, …); /* 0x...5...beef0000 */ buf[0x40] = ‘A’; /* 0x...5...beef0040 */ buf[0xa0] = ‘A’; /* 0x...5...beef00a0 */ @0x…….beef0080: first byte of next alloc … data … @0x…….beef00a0: … data …
pointer tag matches F A I L : p
n t e r t a g m i s m a t c h
heap: saved_actions[] … my_action ... stack: … return address ... int action_launch(int idx) { int (*action)(struct thing *); int rc; ... action = saved_actions[idx]; rc = action(info); … } int my_action(struct thing *info) { stuff; and; things; … return 0; } f
w a r d e d g e backward edge
Ignore function prototype … Normally just a call to a memory address:
Ignore function prototype … Clang - f s a n i t i z e = c f i will check at runtime:
– Clang: -
regular stack: ... all local variables register spills return address ... local variables return address ... safe stack: ... safe variables register spills return address ... unsafe stack: ... buffers by-referenced vars etc ...
– Clang: -
– Results in two stack registers: s
regular stack: ... all local variables register spills return address ... local variables return address ... x18 stack: ... return address return address ... sp stack: ... all local variables register spills etc ...
– Implicit use of otherwise read-only shadow stack during c
– New instructions: p
– Clang and gcc: -
– Finally eradicated from kernel since v4.20 (Dec 2018)!
– Steady progress on full markings (232 of 2311 remain)
– Various partial options, needs more compiler work
– Memory allocations now doing explicit overflow detection – Needs better kernel support for Clang and gcc
– Crying about performance hits – Waiting (im)patiently for hardware support
– Need Clang LTO support in kernel, but works on Android
– Shadow Call Stack works on Android – Waiting (im)patiently for hardware support
https://www.flickr.com/photos/wonderlane/5270711224