Subject: [Phrack] Hardening the Linux Kernel (series 2.0.x)
---[ Phrack Magazine Volume 8, Issue 52 January 26, 1998, article 06 of 20
-------------------------[ Hardening the Linux Kernel (series 2.0.x)
--------[ route|daemon9 <route@infonexus.com>
----[ Introduction and Impetus
Linux. The cutest Unix-like O/S alive today. Everyone knows at least
*one* person who has at least *one* Linux machine. Linux, whatever your
opinion of it, is out there, and is being used by more and more people. Many
of the people using Linux are using it in multi-user environments. All of a
sudden they find security to be a big issue. This article is for those people.
This article covers a few areas of potential insecurity in the Linux O/S
and attempts to improve upon them. It contains several security related
kernel patches for the 2.0.x kernels (each has been tested successfully on the
2.0.3x kernels and most should work on older 2.0.x kernels; see each
subsection for more info).
These are kernel patches. They do nothing for user-land security. If you
can not set permissions and configure services correctly, you should not be
running a Unix machine.
These patches are not bugfixes. They are preventative security fixes.
They are intended to prevent possible problems and breaches of security from
occurring. In some cases they can remove (or at least severely complicate) the
threat of many of today's most popular methods of attack.
These patches are not really useful on a single-user machine. They are
really intended for a multi-user box.
This article is for those of you who want better security out of your Linux
O/S. If you want to go a bit further, look into the POSIX.1e (POSIX 6) stuff.
POSIX.1e is a security model that basically separates identity and privilege.
Effectively, it splits superuser privileges into different `capabilities`.
Additionally, the Linux POSIX.1e (linux-privs) implementation offers a bitmapped
securelevel, kernel-based auditing (userland audit hooks are being developed),
and ACLs. See: http://parc.power.net/morgan/Orange-Linux/linux-privs/index.html
To sum it up, in this article, we explore a few ways to make the multi-user
Linux machine a bit more secure and resilient to attack.
----[ The Patches
procfs patch
------------
Tested on: 2.0.0 +
Author: route
Why should we allow anyone to be able to view info on any process?
Normally, /bin/ps can show process listing for every process in the
kernel's process table, regardless of ownership. A non-privileged user can
see all the running processes on a system. This can include information that
could be used in some forms of known / guessed PID-based attacks, not to
mention the obvious lack of privacy. /bin/ps gets this process information by
reading the /proc filesystem.
The /proc filesystem is a virtual filesystem interface into the O/S which
provides all kinds of good information including the status of various
portions of the running kernel and a list of currently running processes. It
has a filesystem interface, which means it has file-system-like access
controls. As such, we can change the default access permissions on the inode
from 555 to 500.
And that's the patch. We just change the permissions on the inode from
S_IFDIR | S_IRUGO | S_IXUGO to S_IFDIR | S_IRUSR | S_IXUSR.
trusted path execution patch
----------------------------
Tested on: 2.0.0 +
Author: route (2.0.x version, original 1.x patch by merc)
Why should we allow arbitrary programs execution rights?
Consider this scenario: You are the administrator of a multi-user Linux
machine. All of a sudden there is a new bug in the Pentium(tm) processor!
As it happens, this bug causes the CPU to lock up entirely, requiring a cold
reboot. This bug is also exploitable by any user regardless of privilege. All
it necessitates is for the malevolent user to 1) get the source, 2) compile the
exploit, and 3) execute the program.
Whelp... 1) has happened. You cannot prevent anyone from getting it. It's
out there. You could remove permissions from the compiler on your machine or
remove the binary entirely, but this does not stop the user from compiling
the exploit elsewhere, and getting the binary on your machine somehow. You
cannot prevent 2) either. However, if you only allow binaries to be executed
from a trusted path, you can prevent 3) from happening. A trusted path is
one that is inside is a root owned directory that is not group or world
writable. /bin, /usr/bin, /usr/local/bin, are (under normal circumstances)
considered trusted. Any non-root users home directory is not trusted, nor is
/tmp. Be warned: This patch is a major annoyance to users who like to execute
code and scripts from their home directories! It will make you extremely
un-popular as far as these people are concerned. It will also let you sleep
easier at night knowing that no unscrupulous persons will be executing
malicious bits of code on your machine.
Before any call to exec is allowed to run, we open the inode of the
directory that the executable lives in and check ownership and permissions.
If the directory is not owned by root, or is writable to group or other, we
consider that untrusted.
securelevel patch
-----------------
Tested on: 2.0.26 +
Author: route
Damnit, if I set the immutable and append only bits, I did it for a reason.
This patch isn't really much of a patch. It simply bumps the securelevel
up, to 1 from 0. This freezes the immutable and append-only bits on files,
keeping anyone from changing them (from the normal chattr interface). Before
turning this on, you should of course make certain key files immutable, and
logfiles append-only. It is still possible to open the raw disk device,
however. Your average cut and paste hacker will probably not know how to do
this.
stack execution disabling patch and symlink patch
-------------------------------
Tested on: 2.0.30 +
Author: solar designer
From the documentation accompanying SD's patch:
This patch is intended to add protection against two classes of security
holes: buffer overflows and symlinks in /tmp.
Most buffer overflow exploits are based on overwriting a function's return
address on the stack to point to some arbitrary code, which is also put
onto the stack. If the stack area is non-executable, buffer overflow
vulnerabilities become harder to exploit.
Another way to exploit a buffer overflow is to point the return address to
a function in libc, usually system(). This patch also changes the default
address that shared libraries are mmap()ed at to make it always contain a
zero byte. This makes it impossible to specify any more data (parameters
to the function, or more copies of the return address when filling with a
pattern) in an exploit that has to do with ASCIIZ strings (this is the
case for most overflow vulnerabilities).
However, note that this patch is by no means a complete solution, it just
adds an extra layer of security. Some buffer overflow vulnerabilities will
still remain exploitable a more complicated way. The reason for using such
a patch is to protect against some of the buffer overflow vulnerabilities
that are yet unknown.
In this version of my patch I also added a symlink security fix, originally
by Andrew Tridgell. I changed it to prevent from using hard links too, by
simply not allowing non-root users to create hard links to files they don't
own, in +t directories. This seems to be the desired behavior anyway, since
otherwise users couldn't remove such links they just created. I also added
exploit attempt logging, this code is shared with the non-executable stack
stuff, and was the reason to make it a single patch instead of two separate
ones. You can enable them separately anyway.
GID split privilege patch
-------------------------------
Tested on: 2.0.30 +
Author: Original version DaveG, updated for 2.0.33 by route
From the documentation accompanying Dave's original patch:
This is a simple kernel patch that allows you to perform certain
privileged operations with out requiring root access. With this patch
three groups become privileged groups allowed to do different operations
within the kernel.
GID 16 : a program running with group 16 privileges can bind to a
< 1024. This allows programs like: rlogin, rcp, rsh, and ssh
to run setgid 16 instead of setuid 0(root). This also allows
servers that need to run as root to bind to a privileged port
like named, to also run setgid 16.
GID 17 : any program running under GID 17 privileges will be able to
create a raw socket. Programs like ping and traceroute can now
be made to run setgid 17 instead of setuid 0(root).
GID 18 : This group is for SOCK_PACKET. This isn't useful for most people,
so if you don't know what it is, don't worry about it.
Limitations
-----------
Since this is a simple patch, it is VERY limited. First of all, there
is no support for supplementary groups. This means that you can't stack
these privileges. If you need GID 16 and 17, there isn't much you can do
about it.
----[ Installation
This patchfile has been tested and verified to work against the latest
stable release of the linux kernel (as of this writing, 2.0.33). It should
work against other 2.0.x releases as well with little or no modification. THIS
IS NOT A GUARANTEE! Please do not send me your failed patch logs from older
kernels. Take this as a perfect opportunity to upgrade your kernel to the
latest release. Note that several of these patches are for X86-Linux only.
Sorry.
Where `userx` are the usernames of the users you wish to give these
permissions to. Next, fix the corresponding group and permissions on the
binaries you wish to strip root privileges from:
`chgrp raw_sock /bin/ping`
`chmod 2755 /bin/ping`
----[ The patchfile
This patchfile should be extracted with the Phrack Magazine Extraction
Utility included in this (and every) issue.
<++> slinux.patch
diff -ru linux-stock/Documentation/Configure.help linux-patched/Documentation/Configure.help
--- linux-stock/Documentation/Configure.help Fri Sep 5 20:43:58 1997
+++ linux-patched/Documentation/Configure.help Mon Nov 10 22:02:36 1997
@@ -720,6 +720,77 @@
later load the module when you install the JDK or find an interesting
Java program that you can't live without.
+Non-executable user stack area (EXPERIMENTAL)
+CONFIG_STACKEXEC
+ Most buffer overflow exploits are based on overwriting a function's
+ return address on the stack to point to some arbitrary code, which is
+ also put onto the stack. If the stack area is non-executable, buffer
+ overflow vulnerabilities become harder to exploit. However, a few
+ programs depend on the stack being executable, and might stop working
+ unless you also enable GCC trampolines autodetection below, or enable
+ the stack area execution permission for every such program separately
+ using chstk.c. If you don't know what all this is about, or don't care
+ about security that much, say N.
+
+Autodetect GCC trampolines
+CONFIG_STACKEXEC_AUTOENABLE
+ GCC generates trampolines on the stack to correctly pass control to
+ nested functions when calling from outside. This requires the stack
+ being executable. When this option is enabled, programs containing
+ trampolines will automatically get their stack area executable when
+ a trampoline is found. However, in some cases this autodetection can
+ be fooled in a buffer overflow exploit, so it is more secure to
+ disable this option and use chstk.c to enable the stack area execution
+ permission for every such program separately. If you're too lazy,
+ answer Y.
+
+Log buffer overflow exploit attempts
+CONFIG_STACKEXEC_LOG
+ This option enables logging of buffer overflow exploit attempts. No
+ more than one attempt per minute is logged, so this is safe. Say Y.
+
+Process table viewing restriction (EXPERIMENTAL)
+CONFIG_PROC_RESTRICT
+ This option enables process table viewing restriction. Users will only
+ be able to get status of processes they own, with the exception the
+ root user, who can get an entire process table listing. This patch
+ should not cause any problems with other programs but it is not fully
+ tested under every possible contingency. You must enable the /proc
+ filesystem for this option to be of any use. If you run a multi-user
+ system and are reasonably concerned with privacy and/or security, say Y.
+
+Trusted path execution (EXPERIMENTAL)
+CONFIG_TPE
+ This option enables trusted path execution. Binaries are considered
+ `trusted` if they live in a root owned directory that is not group or
+ world writable. If an attempt is made to execute a program from a non
+ trusted directory, it will simply not be allowed to run. This is
+ quite useful on a multi-user system where security is an issue. Users
+ will not be able to compile and execute arbitrary programs (read: evil)
+ from their home directories, as these directories are not trusted.
+ This option is useless on a single user machine.
+
+Trusted path execution (EXPERIMENTAL)
+CONFIG_TPE_LOG
+ This option enables logging of execution attempts from non-trusted
+ paths.
+
+Secure mode (EXPERIMENTAL)
+CONFIG_SECURE_ON
+ This bumps up the securelevel from 0 to 1. When the securelevel is `on`,
+ immutable and append-only bits cannot be set or cleared. If you are not
+ concerned with security, you can say `N`.
+
+Split Network Groups (EXPERIMENTAL)
+CONFIG_SPLIT_GID
+ This is a simple kernel patch that allows you to perform certain
+ privileged operations with out requiring root access. With this patch
+ three groups become privileged groups allowed to do different operations
+ within the kernel.
+ GID 16 allows programs to bind to privledged ports.
+ GID 17 allows programs to open raw sockets.
+ GID 18 allows programs to open sock packets.
+
Processor type
CONFIG_M386
This is the processor type of your CPU. It is used for optimizing
@@ -2951,6 +3020,27 @@
netatalk, new mars-nwe and other file servers. At the time of
writing none of these are available. So it's safest to say N here
unless you really know that you need this feature.
+
+Symlink security fix (EXPERIMENTAL)
+CONFIG_SYMLINK_FIX
+ A very common class of security hole on UNIX-like systems involves
+ a malicious user creating a symbolic link in /tmp pointing at
+ another user's file. When the victim then writes to that file they
+ inadvertently write to the wrong file. Enabling this option fixes
+ this class of hole by preventing a process from following a link
+ which is in a +t directory unless they own the link. However, this
+ fix does not affect links owned by root, since these could only be
+ created by someone having root access already. To prevent someone
+ from using a hard link instead, this fix does not allow non-root
+ users to create hard links in a +t directory to files they don't
+ own. Note that this fix might break things. Only say Y if security
+ is more important.
+
+Log symlink exploit attempts
+CONFIG_SYMLINK_LOG
+ This option enables logging of symlink (and hard link) exploit
+ attempts. No more than one attempt per minute is logged, so this is
+ safe. Say Y.
Minix fs support
CONFIG_MINIX_FS
diff -ru linux-stock/arch/i386/config.in linux-patched/arch/i386/config.in
--- linux-stock/arch/i386/config.in Sun May 12 21:17:23 1996
+++ linux-patched/arch/i386/config.in Sun Nov 9 12:38:27 1997
@@ -35,6 +35,15 @@
tristate 'Kernel support for ELF binaries' CONFIG_BINFMT_ELF
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
tristate 'Kernel support for JAVA binaries' CONFIG_BINFMT_JAVA
+ bool 'Non-executable user stack area (EXPERIMENTAL)' CONFIG_STACKEXEC
+ if [ "$CONFIG_STACKEXEC" = "y" ]; then
+ bool ' Autodetect GCC trampolines' CONFIG_STACKEXEC_AUTOENABLE
+ bool ' Log buffer overflow exploit attempts' CONFIG_STACKEXEC_LOG
+ fi
+ bool ' Restrict process table viewing (EXPERIMENTAL)' CONFIG_PROC_RESTRICT
+ bool ' Trusted path execution (EXPERIMENTAL)' CONFIG_TPE
+ bool ' Log untrusted path execution attempts (EXPERIMENTAL)' CONFIG_TPE_LOG
+ bool ' Split Network GIDs (EXPERIMENTAL)' CONFIG_SPLIT_GID
fi
bool 'Compile kernel as ELF - if your GCC is ELF-GCC' CONFIG_KERNEL_ELF
diff -ru linux-stock/arch/i386/defconfig linux-patched/arch/i386/defconfig
--- linux-stock/arch/i386/defconfig Mon Sep 22 13:44:01 1997
+++ linux-patched/arch/i386/defconfig Sun Nov 9 12:38:23 1997
@@ -24,6 +24,10 @@
CONFIG_SYSVIPC=y
CONFIG_BINFMT_AOUT=y
CONFIG_BINFMT_ELF=y
+# CONFIG_STACKEXEC is not set
+CONFIG_STACKEXEC_AUTOENABLE=y
+CONFIG_STACKEXEC_LOG=y
+CONFIG_SPLIT_GID=y
CONFIG_KERNEL_ELF=y
# CONFIG_M386 is not set
# CONFIG_M486 is not set
@@ -134,6 +138,8 @@
# Filesystems
#
# CONFIG_QUOTA is not set
+# CONFIG_SYMLINK_FIX is not set
+CONFIG_SYMLINK_LOG=y
CONFIG_MINIX_FS=y
# CONFIG_EXT_FS is not set
CONFIG_EXT2_FS=y
@@ -143,6 +149,9 @@
# CONFIG_VFAT_FS is not set
# CONFIG_UMSDOS_FS is not set
CONFIG_PROC_FS=y
+CONFIG_PROC_RESTRICT=y
+CONFIG_TPE=y
+CONFIG_TPE_LOG=y
CONFIG_NFS_FS=y
# CONFIG_ROOT_NFS is not set
# CONFIG_SMB_FS is not set
diff -ru linux-stock/arch/i386/kernel/head.S linux-patched/arch/i386/kernel/head.S
--- linux-stock/arch/i386/kernel/head.S Tue Aug 5 09:19:53 1997
+++ linux-patched/arch/i386/kernel/head.S Sun Nov 9 00:55:50 1997
@@ -400,10 +400,17 @@
.quad 0x0000000000000000 /* not used */
.quad 0xc0c39a000000ffff /* 0x10 kernel 1GB code at 0xC0000000 */
.quad 0xc0c392000000ffff /* 0x18 kernel 1GB data at 0xC0000000 */
+#ifdef CONFIG_STACKEXEC
+ .quad 0x00cafa000000ffff /* 0x23 user 2.75GB code at 0 */
+ .quad 0x00cbf2000000ffff /* 0x2b user 3GB data at 0 */
+ .quad 0x00cbda000000ffff /* 0x32 user 3GB code at 0, DPL=2 */
+ .quad 0x00cbd2000000ffff /* 0x3a user 3GB stack at 0, DPL=2 */
+#else
.quad 0x00cbfa000000ffff /* 0x23 user 3GB code at 0x00000000 */
.quad 0x00cbf2000000ffff /* 0x2b user 3GB data at 0x00000000 */
.quad 0x0000000000000000 /* not used */
.quad 0x0000000000000000 /* not used */
+#endif
.fill 2*NR_TASKS,8,0 /* space for LDT's and TSS's etc */
#ifdef CONFIG_APM
.quad 0x00c09a0000000000 /* APM CS code */
diff -ru linux-stock/arch/i386/kernel/ptrace.c linux-patched/arch/i386/kernel/ptrace.c
--- linux-stock/arch/i386/kernel/ptrace.c Mon Aug 4 12:12:22 1997
+++ linux-patched/arch/i386/kernel/ptrace.c Sun Nov 9 00:55:50 1997
@@ -413,7 +413,7 @@
addr == FS || addr == GS ||
addr == CS || addr == SS) {
data &= 0xffff;
- if (data && (data & 3) != 3)
+ if (data && (data & 3) < 2)
return -EIO;
}
if (addr == EFL) { /* flags. */
@@ -423,6 +423,10 @@
/* Do not allow the user to set the debug register for kernel
address space */
if(addr < 17){
+ if (addr == EIP && (data & 0xF0000000) == 0xB0000000)
+ if (put_stack_long(child, CS*sizeof(long)-MAGICNUMBER, USER_HUGE_CS) ||
+ put_stack_long(child, SS*sizeof(long)-MAGICNUMBER, USER_HUGE_SS))
+ return -EIO;
if (put_stack_long(child, sizeof(long)*addr-MAGICNUMBER, data))
return -EIO;
return 0;
diff -ru linux-stock/arch/i386/kernel/signal.c linux-patched/arch/i386/kernel/signal.c
--- linux-stock/arch/i386/kernel/signal.c Mon Aug 4 12:12:51 1997
+++ linux-patched/arch/i386/kernel/signal.c Sun Nov 9 00:55:50 1997
@@ -83,10 +83,10 @@
#define COPY_SEG(x)
if ( (context.x & 0xfffc) /* not a NULL selectors */
&& (context.x & 0x4) != 0x4 /* not a LDT selector */
- && (context.x & 3) != 3 /* not a RPL3 GDT selector */
+ && (context.x & 3) < 2 /* not a RPL3 or RPL2 GDT selector */
) goto badframe; COPY(x);
#define COPY_SEG_STRICT(x)
-if (!(context.x & 0xfffc) || (context.x & 3) != 3) goto badframe; COPY(x);
+if (!(context.x & 0xfffc) || (context.x & 3) < 2) goto badframe; COPY(x);
struct sigcontext_struct context;
struct pt_regs * regs;
@@ -167,16 +167,20 @@
unsigned long * frame;
frame = (unsigned long *) regs->esp;
- if (regs->ss != USER_DS && sa->sa_restorer)
+ if (regs->ss != USER_DS && regs->ss != USER_HUGE_SS && sa->sa_restorer)
frame = (unsigned long *) sa->sa_restorer;
frame -= 64;
if (verify_area(VERIFY_WRITE,frame,64*4))
do_exit(SIGSEGV);
/* set up the "normal" stack seen by the signal handler (iBCS2) */
+#ifdef CONFIG_STACKEXEC
+ put_user((unsigned long)MAGIC_SIGRETURN, frame);
+#else
#define __CODE ((unsigned long)(frame+24))
#define CODE(x) ((unsigned long *) ((x)+__CODE))
put_user(__CODE,frame);
+#endif
if (current->exec_domain && current->exec_domain->signal_invmap)
put_user(current->exec_domain->signal_invmap[signr], frame+1);
else
@@ -204,19 +208,17 @@
/* non-iBCS2 extensions.. */
put_user(oldmask, frame+22);
put_user(current->tss.cr2, frame+23);
+#ifndef CONFIG_STACKEXEC
/* set up the return code... */
put_user(0x0000b858, CODE(0)); /* popl %eax ; movl $,%eax */
put_user(0x80cd0000, CODE(4)); /* int $0x80 */
put_user(__NR_sigreturn, CODE(2));
#undef __CODE
#undef CODE
+#endif