This blog post, written by Márton Juhász, is the fourth in a series of blog posts on transforming the Raspberry Pi into a security enhanced IoT platform. Previous posts discussed building a custom Linux system with Buildroot, installing OP-TEE, and verified boot on the Raspberry Pi. This post will describe some OS hardening options you can use to reduce the attacks surface.

Some of the hardening options we discuss are build CONFIGs and some of them are runtime settings, but some runtime settings depend on some build CONFIGs. Note that these are for maximum security, so they can cause a performance drop, and they can make some other packages fail during build time or malfunction later.

Buildroot build options and Linux Kernel CONFIGs

These must be set before a system build, alongside with the other configuration settings in Buildroot.

-fstack-protector

Enable stack smashing protection support using GCC’s -fstack-protector option family. See http://www.linuxfromscratch.org/hints/downloads/files/ssp.txt for details.

BR2_SSP_REGULAR: Emit extra code to check for buffer overflows, such as stack smashing attacks. This is done by adding a guard variable to functions with vulnerable objects. This includes functions that call alloca, and functions with buffers larger than 8 bytes. The guards are initialized when a function is entered and then checked when the function exits. If a guard check fails, an error message is printed and the program exits.

BR2_SSP_STRONG: Like -fstack-protector but includes additional functions to be protected – those that have local array definitions, or have references to local frame addresses.

BR2_SSP_ALL: Like -fstack-protector except that all functions are protected. This option might have a significant performance impact on the compiled binaries.

  • Set Build options -> Stack Smashing Protection -> -fstack-protector-all (BR2_SSP_ALL = y).

RELRO

Enable a link-time protection know as RELRO (RELocation Read Only) which helps to protect from certain type of exploitation techniques altering the content of some ELF sections.

BR2_RELRO_PARTIAL: This option makes the dynamic section not writeable after initialization (with almost no performance penalty).

BR2_RELRO_FULL: This option includes the partial configuration, but also marks the GOT as read-only at the cost of initialization time during program loading, i.e every time an executable is started.

  • Set Build options -> RELRO Protection -> Full (BR2_RELRO_FULL = y).

_FORTIFY_SOURCE

Enable the _FORTIFY_SOURCE macro which introduces additional checks to detect buffer-overflows in the following standard library functions: memcpy, mempcpy, memmove, memset, strcpy, stpcpy, strncpy, strcat, strncat, sprintf, vsprintf, snprintf, vsnprintf, gets.

BR2_FORTIFY_SOURCE_1: This option sets _FORTIFY_SOURCE to 1 and only introduces checks that shouldn’t change the behavior of conforming programs. Adds checks at compile-time only.

BR2_FORTIFY_SOURCE_2: This option sets _FORTIFY_SOURCES to 2 and some more checking is added, but some conforming programs might fail. Also adds checks at run-time (detected buffer overflow terminates the program).

  • Set Build options -> Buffer-overflow Detection (FORTIFY_SOURCE) -> Aggressive (BR2_FORTIFY_SOURCE_2 = y).

Password encoding

Choose the password encoding scheme to use when Buildroot needs to encode a password (eg. the root password, below). Note: this is used at build-time, and not at runtime.

BR2_TARGET_GENERIC_PASSWD_MD5: Use MD5 to encode passwords. The default. Wildly available, and pretty good. Although pretty strong, MD5 is now an old hash function, and suffers from some weaknesses, which makes it susceptible to brute-force attacks.

BR2_TARGET_GENERIC_PASSWD_SHA256: Use SHA256 to encode passwords. Very strong, but not ubiquitous, although available in glibc for some time now.

BR2_TARGET_GENERIC_PASSWD_SHA512: Use SHA512 to encode passwords. Extremely strong, but not ubiquitous, although available in glibc for some time now.

  • Set System configuration -> Passwords encoding -> sha-512 (BR2_TARGET_GENERIC_PASSWD_SHA512 = y).

root login

BR2_TARGET_ENABLE_ROOT_LOGIN: Allow root to log in with a password. If not enabled, root will not be able to log in with a password. However, if you have an ssh server and you add an ssh key, you can still allow root to log in.

  • Check System configuration -> Enable root login with password (BR2_TARGET_ENABLE_ROOT_LOGIN = y).

BR2_TARGET_GENERIC_ROOT_PASSWD: Set the initial root password. If set to empty (the default), then no root password will be set, and root will need no password to log in. If the password starts with any of $1$, $5$ or $6$, it is considered to be already crypt-encoded with respectively MD5, SHA256 or SHA512. Any other value is taken to be a clear-text value, and is crypt-encoded as per the “Passwords encoding” scheme, above. WARNING! The password appears as-is in the .config file, and may appear in the build log! Avoid using a valuable password if either the .config file or the build log may be distributed, or at the very least use a strong cryptographic hash for your password!

  • Set System configuration -> Enable root login with password -> Root password to some strong password (BR2_TARGET_GENERIC_ROOT_PASSWD = some_strong_password).

CONFIGs

Add the following lines to buildroot/rpi3.conf or to another additional configuration fragment file.

Report BUG() conditions and kill the offending process:

CONFIG_BUG=y

Make sure kernel page tables have safe permissions:

CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_RODATA=y
CONFIG_STRICT_KERNEL_RWX=y

Report any dangerous memory permissions (not available on all archs):

CONFIG_DEBUG_WX=y

Provides some protections against SYN flooding:

CONFIG_SYN_COOKIES=y

Perform additional validation of various commonly targeted structures:

CONFIG_DEBUG_CREDENTIALS=y
CONFIG_DEBUG_NOTIFIERS=y
CONFIG_DEBUG_LIST=y
CONFIG_DEBUG_SG=y
CONFIG_BUG_ON_DATA_CORRUPTION=y
CONFIG_SCHED_STACK_END_CHECK=y

Provide userspace with seccomp BPF API for syscall attack surface reduction:

CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y

Provide userspace with ptrace ancestry protections:

CONFIG_SECURITY=y
CONFIG_SECURITY_YAMA=y

Perform usercopy bounds checking.

CONFIG_HARDENED_USERCOPY=y

Randomize allocator freelists, harden metadata:

CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y

Allow allocator validation checking to be enabled (see “slub_debug=P” below):

CONFIG_SLUB_DEBUG=y

Wipe higher-level memory allocations when they are freed (needs “page_poison=1” command line below):

CONFIG_PAGE_POISONING=y
CONFIG_PAGE_POISONING_NO_SANITY=n
CONFIG_PAGE_POISONING_ZERO=y

Adds guard pages to kernel stacks (not all architectures support this yet):

CONFIG_VMAP_STACK=y

Perform extensive checks on reference counting:

CONFIG_REFCOUNT_FULL=y

Reboot devices immediately if kernel experiences an Oops:

CONFIG_PANIC_ON_OOPS=y
CONFIG_PANIC_TIMEOUT=-1

Disallow allocating the first 32k of memory (cannot be 64k due to ARM loader):

CONFIG_DEFAULT_MMAP_MIN_ADDR=32768

Make sure PAN emulation is enabled:

CONFIG_ARM64_SW_TTBR0_PAN=y

U-Boot command line options

These must be set before a U-Boot build, alongside with the other configuration settings in U-Boot env config.

  • Enable slub/slab allocator free poisoning (requires CONFIG_SLUB_DEBUG=y above),

  • Enable buddy allocator free poisoning (requires CONFIG_PAGE_POISONING=y above),

  • Disable slab merging (makes many heap overflow attacks more difficult):

  • Add slub_debug=P page_poison=1 slab_nomerge to the end of the string between ‘ ‘ in the line starting with set_common_args in optee/build/rpi3/firmware/uboot.env.txt.

It should look like something like this:

set_common_args=setenv bootargs ${bootargs} smsc95xx.macaddr=${ethaddr} 
'ignore_loglevel dma.dmachans=0x7f35 rootwait 8250.nr_uarts=1 elevator=deadline 
fsck.repair=yes bcm2708_fb.fbwidth=1920 bcm2708_fb.fbheight=1080 
vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000 slub_debug=P 
page_poison=1 slab_nomerge'

sysctls

These must be set in runtime, after a system build and after each boot.

Create a file, named sysctl.conf, and copy to /etc/ on the root partition of the memory card.

# Try to keep kernel address exposures out of various /proc files (kallsyms, modules, etc).
kernel.kptr_restrict = 1

# Avoid kernel memory address exposures via dmesg.
kernel.dmesg_restrict = 1

# Block non-uid-0 profiling (needs distro patch, otherwise this is the same as "= 2")
kernel.perf_event_paranoid = 3

# Avoid non-ancestor ptrace access to running processes and their credentials.
kernel.yama.ptrace_scope = 1

The scripts in /etc/init.d/ run after each boot and before each shutdown. The following script is responsible for setting the sysctls above. So create another, executable file, named S02procps, and copy to /etc/init.d/ on the root partition of the memory card.

#! /bin/sh
if [ "$1" == "start" ]; then
    sysctl -p
fi

The next post will discuss how to make the Raspberry Pi function as a WiFi access point (such that it can perform some gateway functionality).

Sources

  • https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Recommended_Settings
  • Buildroot’s configuration menu’s Help
 

Leave a Reply