Kconfig | A | A+ | F | U | U+ |
---|---|---|---|---|---|
FORTIFY_SOURCE | Y | Y | Y | Y | Y |
LIVEPATCH | N | N | N | Y | Y |
A: Arch 5.1.8,A+: Arch Harden 5.1.11,F: Fedora 5.1.8,U: Ubuntu 5.0.0,U+: Ubuntu LTE 4.15.0
Miscellaneous Topics
Eliminating Variable-length Arrays (VLA)
VLA allows programmers to specify the length of an array at runtime: e.g., using a variable instead of a constant for the array size. This makes it easier to write certain types of programming logics such as packet/buffer handling or string manipulation, but has two critical problems, namely security and performance.
void test_vla(int i) {
long buf[i];
// => 30 instructions w/ div/mul
// char *buf;
// buf -= ((i << 0x3) + 0xf) / 0x10 * 0x10
}
In terms of security,
such a code pattern makes it hard
to estimate the stack usage,
otherwise incurring a stack clash in the kernel space.
Also, although often not emphasized enough,
this pattern
makes it easier to exploit the uninitialized-use vulnerability:
e.g., placing arbitrary data to the proper offset
of the kernel stack.
In terms of performance,
this benign looking code
is translated to
a set of about 30 native instructions
that calculate the proper offset size
and enforce alignment of the stack,
otherwise incurring an exception
in many architectures.
The translated instructions
include a few computational intensive instructions
such as div
and imul
,
so impose unwanted performance overheads
in a common path.
Since v4.20 [1],
the compilation warning on the usage of VLA (i.e., -Wvla
)
has been globally turned on;
any use of VLA prevents
the kernel from compilation,
thereby guiding developers to properly fix
the use of VLA.
Preventing mistakes in switch/case
The usage of switch
case
in C is rather error-prone:
an optional break
statement can be used
in each case
block to indicate the break
of the logic,
otherwise simply executing the next case
block.
As both usage patterns are prevalent,
it is hard to recognize
whether which one is intended code flow or not.
The most recent break
mistake (04/2019 at the time of writing)
is in sock_ioctl()
that is widely used and heavily audited function!
// @net/socket.c
long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg) {
...
case SIOCGSTAMP_OLD:
case SIOCGSTAMPNS_OLD:
if (!sock->ops->gettstamp) {
err = -ENOIOCTLCMD;
break;
}
err = sock->ops->gettstamp(sock, argp,
cmd == SIOCGSTAMP_OLD,
!IS_ENABLED(CONFIG_64BIT));
+ break;
case SIOCGSTAMP_NEW:
case SIOCGSTAMPNS_NEW:
...
break;
To address this error-prone situation,
GCC introduces a compilation warning
on an implicit use of case fall through
(i.e., -Wimplicit-fallthrough
):
to avoid the warning of the fall through case,
developers should explicitly express
the intention, either as a comment
(/* fall through */
) or
as an attribute (__attribute__((fallthrough))
).
+++ b/kernel/compat.c
@@ -346,8 +346,11 @@ get_compat_sigset(...)
return -EFAULT;
switch (_NSIG_WORDS) {
case 4: set->sig[3] = v.sig[6] | (((long)v.sig[7]) << 32 );
+ /* fall through */
case 3: set->sig[2] = v.sig[4] | (((long)v.sig[5]) << 32 );
+ /* fall through */
Fortify
FORTIFY_SOURCE was originally feature from gcc, but adopted to linux kernel later. This option provides support for detecting buffer overflows within various functions. Unfortunately, this option cannot detect all types of buffer overflows(will be discussed in below), but it is useful since it provides extra level of validation with low performance overhead. FORTIFY_SOURCE checks buffer overflow for functions below :
memcpy, mempcpy, memmove, memset, strcpy, stpcpy, strncpy, strcat,
strncat, sprintf, vsprintf, snprintf, vsnprintf, gets
Let's dive into some functions : strcpy() and memcpy().
At first, strcpy() checks object size via __butiltin_object_size(). This function returns object size that is determined on compile-time. However, if the object size is determined on run-time, e.g. object is allocated via kmalloc(), __butiltin_object_size() just returns -1. If both object size determined on run-time, strcpy() skips the overflow tests and passes objects to __builtin_strcpy(). Otherwise, it passes objects to memcpy() which is also fortified. Actual buffer-overflow checks would be done in memcpy(). As you can imagine, fortified strcpy() cannot detect buffer-overflows if size of both objects are determined on run-time, i.e. the case that strcpy passes objects to __builtin_strcpy().
/* defined after fortified strlen and memcpy to reuse them */
__FORTIFY_INLINE char *strcpy(char *p, const char *q)
{
size_t p_size = __builtin_object_size(p, 0);
size_t q_size = __builtin_object_size(q, 0);
if (p_size == (size_t)-1 && q_size == (size_t)-1)
return __builtin_strcpy(p, q);
memcpy(p, q, strlen(q) + 1);
return p;
}
memcpy() also checks object size via __butiltin_object_size(). Both read-overflow and write-overflow check are performed here. If no overflow detected, then it assumes overflow-safe and runs __builtin_memcpy().
__FORTIFY_INLINE void *memcpy(void *p, const void *q, __kernel_size_t size)
{
size_t p_size = __builtin_object_size(p, 0);
size_t q_size = __builtin_object_size(q, 0);
if (__builtin_constant_p(size)) {
if (p_size < size)
__write_overflow();
if (q_size < size)
__read_overflow2();
}
if (p_size < size || q_size < size)
fortify_panic(__func__);
return __builtin_memcpy(p, q, size);
}
References
Livepatch
Livepatch is a feature that applies kernel patches without any system reboot. There are many situations where systems have to keep running and up because of some critical issues such as huge economical costs. For example, In Facebook, it would take about over than 20 minutes to reboot for just one machine. But it is reluctant not to apply patches on kernel when some bugs were found as soon as possible. In order to meet these two requirements, livepatch gives the ways to redirect the buggy code to new code with keeping running.
Consistency model
\textbf{\textcolor{red}{Assigned to Sungbae Yoo}}
Design pattern for modules
\textbf{\textcolor{red}{Assigned to Sungbae Yoo}}
How it works
\textbf{\textcolor{red}{Assigned to Sungbae Yoo}}
Shadow data
\textbf{\textcolor{red}{Assigned to Sungbae Yoo}}
Userspace tool(kpatch)
Kpatch is a feature of the Linux kernel for livepatching made by Red Hat.
kpatch-build is one of the kpatch modules that convert patch files into kernel module.
+---------+ +---------------------+ +--------------+
| patch | | kpatch-build | | patch module |
+---------+ => | ============ | => +--------------+
| *.patch | | Create patch module | | *.ko |
+---------+ +---------------------+ +--------------+
How to make kernel module
- Download and unpack kernel source matching with patches's distro.
- Test patch file with option dry-run
- Read special section data with command (readelf -wi "$VMLINUX")
- alt_instr, bug_entry size, jump_entry size ...
- Build original source with compile options "-ffunction-sections and -fdata-sections"
- Build patched source with compile options "-ffunction-sections and -fdata-sections"
- Extract new and modified ELF sections
- Compare #4's output and #5's output at a section level
- Result: Elf object included {.kpatch.strings, .kpatch.symbols, .kpatch.relocations}
- Build patch module with #6's output
Core data structure: kpatch-elf
kpatch-build uses own data structure which added special data structures to elf format. The special data structures are able to include difference section between the origin object and the patched object.
The intermediate objects of kpatch-build are used in the form of kpatch-elf.
struct kpatch_elf {
Elf *elf;
struct list_head sections;
struct list_head symbols;
struct list_head strings;
int fd;
};
Core module: create-diff-object.c
This file contains the heart of the ELF object differencing engine.
- The tool takes two ELF objects from two versions of the same source file.
- a "base" object and a "patched" object
- These object need to have been compiled with the GCC options.
- -ffunction-sections and -fdata-sections
- The tool compares the objects at a section level to determine what sections have changed.
- Once a list of changed sections has been generated, various rules are applied.