If we enable the “Disable privileged I/O” feature in the hardened kernel and reboot, we can’t start X server. That’s because Xorg uses privileged I/O operations. We might receive an error like this:
[python] # startx xf86EnableIOPorts: failed to set IOPL for I/O (Operation not permitted) [/python]
If we would like to use Xorg, we must enable privileged I/O operations. That disables the “Disable privileged I/O” option in the hardened Linux kernel.
But if we want to have privileged I/O operations disabled, and use Xorg, we can apply a patch to the xorg-server, which can be obtained here. We can apply a custom patch in Gentoo by using the epatch_user function, which applies patches found in /etc/portage/patches/
Whenever we want to enable PaX, we need to choose between two modes:
SOFTMODE: The kernel doesn’t enforce PaX protection by default for features which can be turned on or off at runtime. Non-SOFTMODE: The kernel enforces PaX protections by default for all features.
We already mentioned that PaX supports the following features. They can be set to one of the presented values (summarized after [5]). The letters in the brackets represent the letter, which can be used to control the corresponding feature on a per executable or library basis. We can only enforce the settings for the PAGEEXEC, SEGMEXEC, EMUTRAMP, MPROTECT and RANDMMAP on per object basis. The upper-case letters are used to enforce the setting in softmode, while the lower-case letters are used to relax the setting in non-softmode [5]. The bolded PaX protection features must be applied to ELF executables in order for them to be used, while the other features are applied to the kernel level.
Non-Executable Memory:
PAX_NOEXEC: Enforces all segments (except .text) of program as non-executable when loaded in memory. PAGEEXEC (p/P): The NX-bit used by hardware CPU to enforce non-executable bit on memory pages. SEGMEXEC (s/S): The NX-bit used by hardware CPU to enforce non-executable bit on memory segments. EMUTRAMP (e/E): Allows emulation of trampolines, even when the memory is marked as non-executable. This is normally used by self-modifying code, which is often used in viruses and worms, but can have a legitimate purposes as well. MPROTECT (m/M): Prevents changing memory access, creation of anonymous RWX memory and making relro data pages writable. KERNEXEC: Enforces PAGEEXEC and MPROTECT in the kernel space.
ASLR:
PAX_ASLR: Expands the number of randomized bits of the address space. RANDMMAP (r/R): Enforces the use of a randomized base address. RANDKSTACK: Enforces the use of a randomized stack address in every kernel’s process. RANDUSTACK: Enforces the use of a randomized stack address in every user’s process.
Miscellaneous Memory Protections:
STACKLEAK: Deletes the kernel stack before the system call returns. UDEREF: Prevents the kernel from dereferencing user-land pointers when kernel pointers are expected. REFCOUNT: Prevents the kernel from overflowing reference counters. USERCOPY: Makes the kernel enforce the size of heap objects when copied between user and kernel land. SIZE_OVERFLOW: Makes the kernel recompute function arguments with double integer precision. LATENT_ENTROPY: Makes the kernel generate extra entropy during system boots.
There are two ways of setting PaX enforcements on the binary programs, which are presented below. We usually should choose one of the options. If all are enabled, the PaX flags should be the same for all of them. By changing PaX flags, we’re effectively enforcing or relaxing some PaX restrictions on ELF executable. That might be needed when the program uses memory in a way that isn’t allowed.
PT_PAX: Keeps PaX flags in the ELF program header. That’s useful, because flags are carried around with binary program. But that introduces other kinds of problems, which is why it’s better to use XATTR_PAX. XATTR_PAX: Keeps PaX flags in the filesystem’s extended attributes, which doesn’t modify the ELF binary. The only requirement is that the filesystem used supports xattrs.
PaX flags are enforced only on processes that were started from ELF executables. Such executables normally use shared libraries, which have their own PaX flags. The PaX flags of the process can be seen in the status file of each process in the /proc directory. Below, we’ve printed the PaX flags of the process with PID 11788. [python] # cat /proc/11788/status | grep PaX PaX: PemRs [/python] PaX enforcement flags set on processes are those set by the program’s ELF executable, and not one of its shared libraries. That’s because a program normally links against multiple shared libraries, and there’s no way to actually determine which shared library would take preference when setting PaX flags. Also, if the PaX flags of chosen shared libraries are poorly set, that would affect the security of the whole system. Let’s install a few of the tools that we need when working with PaX enabled executables. The command to install the most important packages regarding PaX is presented below: [python] # emerge app-misc/pax-utils sys-apps/paxctl app-admin/paxtest sys-apps/attr sys-apps/elfix [/python] The paxctl feature can be used to set only PT_PAX, while the paxctl-ng feature can set both PT_PAX and XATTR_PAX flags. There’s also a simple tool called migrate-pax, which copies the PT_PAX flags to the XATTR_PAX for each program. Alternatively, we can use the paxtest tool, which checks how the PaX settings affect our system, by trying to attack it. There are a number of test cases, which are done by this tool and available to the user. An example of running paxtest on non-hardened kernel is presented below. [python] # paxtest kiddie PaXtest – Copyright(c) 2003,2004 by Peter Busser <peter@adamantix.org> Released under the GNU Public Licence version 2 or later Writing output to paxtest.log It may take a while for the tests to complete Test results: PaXtest – Copyright(c) 2003,2004 by Peter Busser <peter@adamantix.org> Released under the GNU Public Licence version 2 or later Mode: kiddie Linux user 3.4.9-gentoo #9 SMP PREEMPT Sat Mar 9 11:52:45 CET 2013 x86_64 Intel(R) Core(TM)2 Duo CPU P8800 @ 2.66GHz GenuineIntel GNU/Linux Executable anonymous mapping : Killed Executable bss : Killed Executable data : Killed Executable heap : Killed Executable stack : Killed Executable shared library bss : Killed Executable shared library data : Killed Executable anonymous mapping (mprotect) : Vulnerable Executable bss (mprotect) : Vulnerable Executable data (mprotect) : Vulnerable Executable heap (mprotect) : Vulnerable Executable stack (mprotect) : Vulnerable Executable shared library bss (mprotect) : Vulnerable Executable shared library data (mprotect): Vulnerable Writable text segments : Vulnerable Anonymous mapping randomisation test : 28 bits (guessed) Heap randomisation test (ET_EXEC) : 14 bits (guessed) Heap randomisation test (PIE) : 28 bits (guessed) Main executable randomisation (ET_EXEC) : No randomisation Main executable randomisation (PIE) : 28 bits (guessed) Shared library randomisation test : 28 bits (guessed) Stack randomisation test (SEGMEXEC) : 28 bits (guessed) Stack randomisation test (PAGEEXEC) : 28 bits (guessed) Return to function (strcpy) : paxtest: return address contains a NULL byte. Return to function (memcpy) : Vulnerable Return to function (strcpy, PIE) : paxtest: return address contains a NULL byte. Return to function (memcpy, PIE) : Vulnerable [/python] In the output above, there are various vulnerable test cases. They’re fixed in the hardened kernel, as seen below. [python] # paxtest kiddie PaXtest – Copyright(c) 2003,2004 by Peter Busser <peter@adamantix.org> Released under the GNU Public Licence version 2 or later Writing output to paxtest.log It may take a while for the tests to complete Test results: PaXtest – Copyright(c) 2003,2004 by Peter Busser <peter@adamantix.org> Released under the GNU Public Licence version 2 or later Mode: kiddie Linux user 3.10.1-hardened-r1 #10 SMP PREEMPT Mon Sep 30 18:29:13 CEST 2013 x86_64 Intel(R) Core(TM)2 Duo CPU P8800 @ 2.66GHz GenuineIntel GNU/Linux Executable anonymous mapping : Killed Executable bss : Killed Executable data : Killed Executable heap : Killed Executable stack : Killed Executable shared library bss : Killed Executable shared library data : Killed Executable anonymous mapping (mprotect) : Killed Executable bss (mprotect) : Killed Executable data (mprotect) : Killed Executable heap (mprotect) : Killed Executable stack (mprotect) : Killed Executable shared library bss (mprotect) : Killed Executable shared library data (mprotect): Killed Writable text segments : Killed Anonymous mapping randomisation test : 29 bits (guessed) Heap randomisation test (ET_EXEC) : 23 bits (guessed) Heap randomisation test (PIE) : 35 bits (guessed) Main executable randomisation (ET_EXEC) : No randomisation Main executable randomisation (PIE) : 27 bits (guessed) Shared library randomisation test : 29 bits (guessed) Stack randomisation test (SEGMEXEC) : 35 bits (guessed) Stack randomisation test (PAGEEXEC) : 35 bits (guessed) Return to function (strcpy) : paxtest: return address contains a NULL byte. Return to function (memcpy) : Vulnerable Return to function (strcpy, PIE) : paxtest: return address contains a NULL byte. Return to function (memcpy, PIE) : Vulnerable [/python] RBAC RBAC operates with roles, which define the operations that can be done on objects on the system. To ensure that users are only allowed to do certain operations, each user must be assigned a RBAC role. Therefore, RBAC is used whenever we would like to restrict access to resources to authorized users. While Grsecurity and PaX are used to prevent attackers being able to gain code execution on the system, RBAC exists to prevent authorized users from doing something they shouldn’t be doing. If we’d like to use RBAC, we first need to enable it in the kernel. We’ve described the “Role Based Access Control Options“, which are used to specify the kernel options used in RBAC. Those kernel options can be seen below.
When we choose to enable the RBAC system, we should install the gradm program. It’s used as an administrative interface to the RBAC system. After we install the gradm package, we should set the administrator password (with the -P option) or enable the Grsecurity RBAC system (with the -E option). We can disable it with -D option as well. [python] # gradm -P Setting up grsecurity RBAC password Password: Re-enter Password: Password written to /etc/grsec/pw.
gradm -E [/python]
After enabling the RBAC, we can configure the system-wide rules through the /etc/grsec/policy file. To ease the configuration of the policy file, the gradm command has the —learn argument. We can use it to build policy files automatically, by learning. The /etc/grsec/policy file consists of three types of objects:
Roles: Users and groups on the system Subjects: Processes and directories Objects: Files and PaX flags
For example, the RBAC can be used to restrict access to ssh. Rules can be put in place to prevent some users from sshing into the box, but enable them to use scp to transfer files between systems. One really important example of using RBAC controls is when a root-owned binary has the SUID/SGID bits set. In that case, any user who runs the executable will run it in the context of the root user. To prevent that, we can enable appropriate RBAC rules, which will give only certain users access to that executable. Signed Kernel Modules When an attacker gains access to our computer, he or she can load a kernel module into the kernel to get a permanent backdoor into the system. That usually happens with rootkits. That problem can be prevented by digitally signing kernel modules, which is supported from kernel version 3.7. If we’d like to enable this option, it can be found under “Enable loadable module support” and can be seen in the picture below as “Module signature verification”.
If we’d like to enable the verification of kernel module signatures, we need to enable the following options:
Module Signature verification:
Require modules to be validly signed: When this option is enabled, all kernel modules need to be signed, otherwise they won’t be allowed to be loaded into the kernel. Automatically sign all modules: This option needs to be enabled if we’d like to sign all kernel modules when compiling the kernel. Which hash algorithm should modules be signed with: This option specifies the algorithm used to sign the modules with. We can choose between: sha-1, sha-224, sha-256, sha-384, and sha-512.
When enabling those options and running “make && make modules && make modules_install,” the modules will be signed with a dynamically built key. That’ll be saved under the root of the kernel source, as signing_key.priv and signing_key.x509. If we’d like to use our own certificates, we need to create them with the openssl command and replace the signing_key.priv and signing_key.x509 keys. Remember that after we’ve built the kernel, we should move the private key .priv to a secure location. If we keep it in the /usr/src/linux/ directory, it can be used by the attacker to sign its own modules, which can then be inserted into the kernel.
ClamAV in Realtime If we’d like to harden files downloaded from the internet, we should use ClamAV anti-virus software. First, we have to install the required packages, which we can do with the command below: [python] # emerge app-antivirus/clamav app-antivirus/clamav-unofficial-sigs app-antivirus/clamtk net-proxy/squidclamav sys-fs/clamfs sys-fs/avfs [/python] Then, we need to update the virus database with the freshclam command, which downloads the main.cvd, daily.cvd and bytecode.cvd that contain signatures used in virus detection. Then, we should download the EICAR virus from the official website onto our disk. We should download the EICAR virus saved in .com and .txt files as well as the one embedded in a single and double zip archive. [python] # wget http://www.eicar.org/download/eicar.com # wget http://www.eicar.org/download/eicar.com.txt # wget http://www.eicar.org/download/eicar_com.zip # wget http://www.eicar.org/download/eicarcom2.zip # cat eicar.com X5O!P%@AP[4PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* [/python] After that, we should scan that directory for malicious files with the clamscan command. [python] # clamscan . ./eicar.com: Eicar-Test-Signature FOUND ./eicar_com.zip: Eicar-Test-Signature FOUND ./eicarcom2.zip: Eicar-Test-Signature FOUND ./eicar.com.txt: Eicar-Test-Signature FOUND ———– SCAN SUMMARY ———– Known viruses: 2820461 Engine version: 0.97.8 Scanned directories: 1 Scanned files: 4 Infected files: 4 Data scanned: 0.00 MB Data read: 0.00 MB (ratio 0.00:1) Time: 5.975 sec (0 m 5 s) [/python] But after downloading the file from the internet, we don’t want to be running clamscan automatically every time. That’s because it’ll quickly become tiresome and we’ll probably stop doing it, which completely defeats the purpose. Instead of doing that, we can run the clamd daemon, and then scan the directory with the clamdscan command. But how is that better than before? We still need to run a command line program to actually scan the file. A good thing about the ClamAV daemon is that all programs in the system can use it to scan files for viruses. [python] # /etc/init.d/clamd start # rc-update add clamd default # clamdscan . ./eicar_com.zip: Eicar-Test-Signature FOUND ./eicar.com: Eicar-Test-Signature FOUND ./eicarcom2.zip: Eicar-Test-Signature FOUND ./eicar.com.txt: Eicar-Test-Signature FOUND ———– SCAN SUMMARY ———– Infected files: 4 Time: 0.002 sec (0 m 0 s) [/python] After that, we still need to solve the problem of automatically scanning the files once they’re downloaded. One solution is to use ClamFS, which is a user-space filesystem that scans every file on the mounted filesystem when we try to access it. The files aren’t necessarily scanned once they are downloaded from the internet, but when accessed by the user. Nevertheless, this is just as secure, because for some attack vectors to be triggered, the user needs to open file first. The second option is Avfs, which is quite similar to ClamFS, except it can also quarantine and isolate infected files, so they will never touch the disk. Plus the user processes won’t be able to access them. ClamFS After installing clamfs, we need to edit the /etc/clamfs/clamfs.xml file to fit our needs. In the xml configuration file, we need to provide the following:
Clamd Socket: We must specify the path to the socket used by clamd.
Filesystem Root: The root directory, where we need to save files, which will be scanned by ClamAV anti-virus. Remember that we shouldn’t open the files saved in this directory.
Filesystem Mountpoint: A copy of the root directory, where the files will be automatically scanned for viruses when opened.
Maximum File Size: We can specify the maximum file size, which will be scanned by ClamAV. Files, which are larger than that won’t be scanned for viruses.
Whitelist Files: The
After we’ve configured clamfs, we need to start it and add it to the default runlevel, so it’ll start at boot time. [python] # /etc/init.d/clamfs start # rc-update add clamfs default [/python] Then, we can copy the previously downloaded EICAR viruses to the configuration’s root directory. That’s where we should copy the files to be scanned. After copying the file into the root directory, the same file will be available in root as well as in the mountpoint location. [python] # cp rootdir/eicar.com.txt mountdir/ [/python] We should remember that we should open files from mountpoint location if we’d like them to be scanned by ClamAV once accessed. The picture below shows the eicar.com.txt file, which was opened from the root directory. We can see that the EICAR virus is there and wasn’t blocked, which is because we’ve opened the file from the wrong directory.
But if we open the same eicar.com.txt file from the mountpoint directory, we can see that the EICAR virus isn’t there anymore, because it was removed.
At that time, we accessed the eicar.com.txt file. That’s why it was scanned by the ClamAV anti-virus scanner. That can be verified by using logging options, where the message about scanning and detecting a virus is appended to the logfile. That can be seen below, where we can see that the process starts to scan the eicar.com.txt file by connecting to the clamd daemon. It detected the Eicar-Test-Signature. [python] 00:33:33 (clamfs.cxx:590) Extension not found in unordered_map 00:33:33 (clamfs.cxx:675) early cache miss for inode 68137890 00:33:33 (clamav.cxx:101) attempt to scan file /home/user/rootdir/eicar.com.txt 00:33:33 (clamav.cxx:111) started scanning file /home/user/rootdir/eicar.com.txt 00:33:33 (clamav.cxx:58) attempt to open control connection to clamd via /var/run/clamav/clamd.sock 00:33:33 (clamav.cxx:63) connected to clamd 00:33:33 (clamav.cxx:88) closing clamd connection 00:33:33 (clamav.cxx:126) /home/user/rootdir/eicar.com.txt: Eicar-Test-Signature FOUND 00:33:33 (clamav.cxx:138) (gvim:23343) (eleanor:1000) /home/user/rootdir/eicar.com.txt: Eicar-Test-Signature FOUND [/python] Conclusion In this article, we’ve presented various techniques that we can use to harden the security of a Linux system. It’s also great to have a table where we can check whether certain security enhancements have been applied to our system. That can help us when securing our system so that we don’t forget anything important. Note that the table was summarized after [12]. References: [1] Hardened Gentoo http://www.gentoo.org/proj/en/hardened/. [2] Security-Enhanced Linux http://en.wikipedia.org/wiki/Security-Enhanced_Linux. [3] RSBAC http://en.wikipedia.org/wiki/RSBAC. [4] Hardened/Toolchain https://wiki.gentoo.org/wiki/Hardened/Toolchain#RELRO. [5] Hardened/PaX Quickstart https://wiki.gentoo.org/wiki/Project:Hardened/PaX_Quickstart. [6] checksec.sh http://www.trapkit.de/tools/checksec.html. [7] KERNHEAP http://subreption.com/products/kernheap/. [8] Advanced Portage Features http://www.gentoo.org/doc/en/handbook/handbook-amd64.xml?part=3&chap=6. [9] Elfix http://dev.gentoo.org/~blueness/elfix/. [10] Avfs: An On-Access Anti-Virus File System http://www.fsl.cs.sunysb.edu/docs/avfs-security04/. [11] Eicar Download, http://www.eicar.org/85-0-Download.html. [12] Gentoo Security Handbook, http://www.gentoo.org/doc/en/security/security-handbook.xml.