Hardening is a set of mechanisms that can be activated by turning on several compiler flags, and are commonly used to protect resulting programs against memory corruption attacks. These mechanisms have been standard practice since at least 2011, when the Debian project set on the goal of releasing all their packages with the security hardening build flags enabled [1]. Ubuntu has also followed the same policy regarding their own build procedures [2].

Kurento Media Server had been lagging in this respect, and old releases only implemented the standard Debian hardening options that are applied by the dpkg-buildflags tool by default:

Format string checks (-Wformat-Werror=format-security) [3]. These options instruct the compiler to make sure that the arguments supplied to string functions such as printf and scanf have types appropriate to the format string specified, and that the conversions specified in the format string make sense.

Fortify Source (-D_FORTIFY_SOURCE=2) [4]. When this macro is defined at compilation time, several compile-time and run-time protections are enabled around unsafe use of some glibc string and memory functions such as memcpy and strcpy, with get replaced by their safer counterparts. This feature can prevent some buffer overflow attacks, but it requires optimization level -O1 or higher so it is not enabled in Debug builds (which use -O0).

Read-Only Relocations (RELRO) (-Wl,-z,relro). This linker option marks any regions of the relocation table as “read-only” if they were resolved before execution begins. This reduces the possible areas of memory in a program that can be used by an attacker that performs a successful GOT-overwrite memory corruption exploit. This option works best with the linker’s Immediate Binding mode, which forces all regions of the relocation table to be resolved before execution begins. However, immediate binding is disabled by default.

Starting from version 6.7, KMS also implements these extra hardening measurements:

Position Independent Code (-fPIC) / Position Independent Executable (-fPIE-pie) [6]. Allows taking advantage of the Address Space Layout Randomization (ASLR) protection offered by the Kernel. This protects against Return-Oriented Programming (ROP) attacks and generally frustrates memory corruption attacks. This option was initially made the default in Ubuntu 16.10 for some selected architectures, and in Ubuntu 17.10 was finally enabled by default across all architectures supported by Ubuntu.

Note

The PIC/PIE feature adds a very valuable protection against attacks, but has one important requisite: all shared objects must be compiled as position-independent code. If your shared library has stopped linking with KMS, or your plugin stopped loading at run-time, try recompiling your code with the -fPIC option.

Immediate Binding (-Wl,-z,now). This linker option improves upon the Read-Only Relocations (RELRO) option, by forcing that all dynamic symbols get resolved at start-up (instead of on-demand). Combined with the RELRO flag, this means that the GOT can be made entirely read-only, which prevents even more types of GOT-overwrite memory corruption attacks.

Since version 6.7, Kurento Media Server is built with all the mentioned hardening measurements. All required flags are added in the Debian package generation step, by setting the environment variable DEB_BUILD_MAINT_OPTIONS to hardening=+all, as described by Debian hardening options. This variable is injected into the build environment by the CMake module kms-cmake-utils/CMake/CommonBuildFlags.cmake, which is included by all modules of KMS.

This section explains how the Position Independent Code (PIC) and Position Independent Executable (PIE) features are intended to be used (in GCC). Proper use of these is required to achieve correct application of ASLR by the Kernel.

PIC must be enabled in the compiler for compilation units that will end up linked into a shared library. Note that this includes objects that get packed as a static library before being linked into a shared library.

PIC must be enabled in the linker for shared libraries.

PIE or PIC (the former being the recommended one) must be enabled in the compiler for compilation units that will end up linked into an executable file. Note that this includes objects that get packed as a static library before being linked into the executable.

PIE must be enabled in the linker for executable files.

Now follows some examples of applying these rules into an hypothetical project composed of one shared library and one executable file:

The shared library (SHARED.so) is composed of 4 source files:

A.c and B.c are compiled first into a static library: AB.a.
GCC flags: -fPIC.

(*): In these cases, it is also possible to compile these files with -fPIC, although -fPIE is recommended. It is also possible to mix both; for example E.c and F.c can be compiled with -fPIC, while G.c and H.c are compiled with -fPIE (empirically tested, it works fine).

CMake has partial native support to enable PIC/PIE in a project, via the POSITION_INDEPENDENT_CODE and CMAKE_POSITION_INDEPENDENT_CODE variables. We consider it “partial” because these variables add the corresponding flags for the compilation steps, but the flag -pie is not automatically added to the linker step.

It would be nice if CMake took over the whole process of generating valid PIC/PIE libraries and executables, by ensuring that all needed flags are added in the correct places. It’s actually very close to that, by only missing the -pie flag while linking executable programs.

Kurento has been supported under Project LERNIM (RTC-2016-4674-7), co-funded by the Ministry of Economy, Finance and Competitiveness of Spain, as well as by the European Regional Development Fund, whose main goal is to promote technological development, innovation and high-quality research.