lkml.org 
[lkml]   [2012]   [Jan]   [9]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patches in this message
/
Date
From
Subject[PATCH] New PT_GNU_COMPAT segment header extension
Hi,

These patches introduces the PT_GNU_COMPAT segment header to indicate the
kernel whether an ELF binary or a shared library needs to have all readable
virtual memory mappings also executable (i.e. READ_IMPLIES_EXEC personality)
and if necessary, it allows more compatibility modes to be implemented in
the future.

Currently, an binary that needs only executable stack have unnecessarily all
readable virtual memory mappings also executable. This is because the kernel
does not know if the PF_X flag set in the PT_GNU_STACK segment header of the
binary indicates it needs only stack executable or all readable virtual
memory mappings also executable. Consequently, the kernel sets the
READ_IMPLIES_EXEC personality upon loading any binary with the PT_GNU_STACK
segment header and the PF_X flag set.

Furthermore, if the PT_GNU_STACK segment header with the PT_X flag is set in
any of the shared libraries a binary is linked, the kernel does not know if
the PF_X flag set in the PT_GNU_STACK segment header of the shared library
indicates it needs only stack executable or all readable virtual memory
mappings also executable either. Consequently, GCC sets the PT_GNU_STACK
segment header with the PF_X flag set upon compiling any binary linked to a
shared library with the PT_GNU_STACK segment header and the PF_X flag set.

This can result in applications unnoticeably having not only the stack, but
also all readable virtual memory mappings also executable. These patches
returns the original meaning and purpose of the PT_GNU_STACK segment header.

From the security standpoint, these binaries will rely upon lesser security
extensions, such as heap consistency checking and others. Thus, improving
the security of binaries that needs only the stack executable.

These patches does not affect the current binaries that have the
PT_GNU_STACK segment header, neither legacy binaries that most likely does
not have this segment header either. For the existing binaries that
necessarily need to have all readable virtual memory mappings also
executable, the PT_GNU_STACK segment header can be easily converted to a
PT_GNU_COMPAT segment header without recompilation.

The following are important details about the changes made to the linker:

* The PT_GNU_STACK and PT_GNU_COMPAT segment headers are mutually
exclusive.
* The PT_GNU_STACK segment header has precedence over the PT_GNU_COMPAT
segment header.
* The PT_GNU_COMPAT segment header IS NOT created by default.
* The PT_GNU_STACK segment header IS ALWAYS created by default--which is
the original behaviour of the linker.

These patches are completely non-intrusive. Over the time, once this
compatibility mode is no longer needed, they can be removed with no
subsequent effect.


--
Ramon de C Valle / Red Hat Security Response Team
diff -wurN a/bfd/elf-bfd.h b/bfd/elf-bfd.h
--- a/bfd/elf-bfd.h 2011-08-16 21:39:38.000000000 -0300
+++ b/bfd/elf-bfd.h 2012-01-07 19:34:53.643000012 -0200
@@ -1600,6 +1600,9 @@
/* Segment flags for the PT_GNU_STACK segment. */
unsigned int stack_flags;

+ /* Segment flags for the PT_GNU_COMPAT segment. */
+ unsigned int compat_flags;
+
/* Symbol version definitions in external objects. */
Elf_Internal_Verdef *verdef;

diff -wurN a/bfd/elf.c b/bfd/elf.c
--- a/bfd/elf.c 2011-07-03 10:37:06.000000000 -0300
+++ b/bfd/elf.c 2012-01-09 08:58:01.230000009 -0200
@@ -1158,6 +1158,7 @@
case PT_GNU_EH_FRAME: pt = "EH_FRAME"; break;
case PT_GNU_STACK: pt = "STACK"; break;
case PT_GNU_RELRO: pt = "RELRO"; break;
+ case PT_GNU_COMPAT: pt = "COMPAT"; break;
default: pt = NULL; break;
}
return pt;
@@ -2118,6 +2119,7 @@
static const struct bfd_elf_special_section special_sections_n[] =
{
{ STRING_COMMA_LEN (".note.GNU-stack"), 0, SHT_PROGBITS, 0 },
+ { STRING_COMMA_LEN (".note.GNU-compat"),0, SHT_PROGBITS, 0 },
{ STRING_COMMA_LEN (".note"), -1, SHT_NOTE, 0 },
{ NULL, 0, 0, 0, 0 }
};
@@ -2484,6 +2486,9 @@
case PT_GNU_RELRO:
return _bfd_elf_make_section_from_phdr (abfd, hdr, hdr_index, "relro");

+ case PT_GNU_COMPAT:
+ return _bfd_elf_make_section_from_phdr (abfd, hdr, hdr_index, "compat");
+
default:
/* Check for any processor-specific program segment types. */
bed = get_elf_backend_data (abfd);
@@ -3558,6 +3563,12 @@
++segs;
}

+ if (elf_tdata (abfd)->compat_flags)
+ {
+ /* We need a PT_GNU_COMPAT segment. */
+ ++segs;
+ }
+
for (s = abfd->sections; s != NULL; s = s->next)
{
if ((s->flags & SEC_LOAD) != 0
@@ -4160,6 +4171,21 @@
}
}

+ if (elf_tdata (abfd)->compat_flags)
+ {
+ amt = sizeof (struct elf_segment_map);
+ m = (struct elf_segment_map *) bfd_zalloc (abfd, amt);
+ if (m == NULL)
+ goto error_return;
+ m->next = NULL;
+ m->p_type = PT_GNU_COMPAT;
+ m->p_flags = elf_tdata (abfd)->compat_flags;
+ m->p_flags_valid = 1;
+
+ *pm = m;
+ pm = &m->next;
+ }
+
free (sections);
elf_tdata (abfd)->segment_map = mfirst;
}
@@ -5434,7 +5460,8 @@
6. PT_TLS segment includes only SHF_TLS sections.
7. SHF_TLS sections are only in PT_TLS or PT_LOAD segments.
8. PT_DYNAMIC should not contain empty sections at the beginning
- (with the possible exception of .dynamic). */
+ (with the possible exception of .dynamic).
+ 9. PT_GNU_COMPAT segments do not include any sections. */
#define IS_SECTION_IN_INPUT_SEGMENT(section, segment, bed) \
((((segment->p_paddr \
? IS_CONTAINED_BY_LMA (section, segment, segment->p_paddr) \
@@ -5442,6 +5469,7 @@
&& (section->flags & SEC_ALLOC) != 0) \
|| IS_NOTE (segment, section)) \
&& segment->p_type != PT_GNU_STACK \
+ && segment->p_type != PT_GNU_COMPAT \
&& (segment->p_type != PT_TLS \
|| (section->flags & SEC_THREAD_LOCAL)) \
&& (segment->p_type == PT_LOAD \
diff -wurN a/bfd/elflink.c b/bfd/elflink.c
--- a/bfd/elflink.c 2011-11-21 07:29:26.000000000 -0200
+++ b/bfd/elflink.c 2012-01-07 19:34:53.693000010 -0200
@@ -5535,6 +5535,8 @@
elf_tdata (output_bfd)->stack_flags = PF_R | PF_W | PF_X;
else if (info->noexecstack)
elf_tdata (output_bfd)->stack_flags = PF_R | PF_W;
+ else if (info->compat)
+ elf_tdata (output_bfd)->compat_flags = PF_R | PF_W | PF_X;
else
{
bfd *inputobj;
diff -wurN a/binutils/readelf.c b/binutils/readelf.c
--- a/binutils/readelf.c 2011-09-21 17:49:13.000000000 -0300
+++ b/binutils/readelf.c 2012-01-07 19:34:54.027000013 -0200
@@ -2786,6 +2786,7 @@
return "GNU_EH_FRAME";
case PT_GNU_STACK: return "GNU_STACK";
case PT_GNU_RELRO: return "GNU_RELRO";
+ case PT_GNU_COMPAT: return "GNU_COMPAT";

default:
if ((p_type >= PT_LOPROC) && (p_type <= PT_HIPROC))
diff -wurN a/include/bfdlink.h b/include/bfdlink.h
--- a/include/bfdlink.h 2011-11-21 07:29:37.000000000 -0200
+++ b/include/bfdlink.h 2012-01-07 19:34:54.401000009 -0200
@@ -395,6 +395,9 @@
flags. */
unsigned int noexecstack: 1;

+ /* TRUE if PT_GNU_COMPAT segment should be created. */
+ unsigned int compat: 1;
+
/* TRUE if we want to produced optimized output files. This might
need much more time and therefore must be explicitly selected. */
unsigned int optimize: 1;
diff -wurN a/include/elf/common.h b/include/elf/common.h
--- a/include/elf/common.h 2011-07-22 17:22:36.000000000 -0300
+++ b/include/elf/common.h 2012-01-07 19:34:54.387000009 -0200
@@ -430,6 +430,7 @@
#define PT_SUNW_EH_FRAME PT_GNU_EH_FRAME /* Solaris uses the same value */
#define PT_GNU_STACK (PT_LOOS + 0x474e551) /* Stack flags */
#define PT_GNU_RELRO (PT_LOOS + 0x474e552) /* Read-only after relocation */
+#define PT_GNU_COMPAT (PT_LOOS + 0x474e553) /* GNU compatibility mode flags */

/* Program segment permissions, in program header p_flags field. */

diff -wurN a/ld/emultempl/elf32.em b/ld/emultempl/elf32.em
--- a/ld/emultempl/elf32.em 2011-11-21 07:29:39.000000000 -0200
+++ b/ld/emultempl/elf32.em 2012-01-07 19:34:52.612000009 -0200
@@ -2285,6 +2285,10 @@
link_info.noexecstack = TRUE;
link_info.execstack = FALSE;
}
+ else if (strcmp (optarg, "enablecompat") == 0)
+ {
+ link_info.compat = TRUE;
+ }
EOF
if test x"$GENERATE_SHLIB_SCRIPT" = xyes; then
fragment <<EOF
@@ -2402,6 +2406,8 @@
-z defs Report unresolved symbols in object files.\n"));
fprintf (file, _("\
-z execstack Mark executable as requiring executable stack\n"));
+ fprintf (file, _("\
+ -z enablecompat Create PT_GNU_COMPAT program header\n"));
EOF

if test x"$GENERATE_SHLIB_SCRIPT" = xyes; then
diff -wurN a/ld/ldgram.y b/ld/ldgram.y
--- a/ld/ldgram.y 2011-07-11 12:03:09.000000000 -0300
+++ b/ld/ldgram.y 2012-01-07 19:34:52.601000009 -0200
@@ -1215,6 +1215,8 @@
$$ = exp_intop (0x6474e550);
else if (strcmp (s, "PT_GNU_STACK") == 0)
$$ = exp_intop (0x6474e551);
+ else if (strcmp (s, "PT_GNU_COMPAT") == 0)
+ $$ = exp_intop (0x6474e553);
else
{
einfo (_("\diff --git a/arch/x86/include/asm/elf.h b/arch/x86/include/asm/elf.h
index 5f962df..4cfc625 100644
--- a/arch/x86/include/asm/elf.h
+++ b/arch/x86/include/asm/elf.h
@@ -259,8 +259,8 @@ extern int force_personality32;
* An executable for which elf_read_implies_exec() returns TRUE will
* have the READ_IMPLIES_EXEC personality flag set automatically.
*/
-#define elf_read_implies_exec(ex, executable_stack) \
- (executable_stack != EXSTACK_DISABLE_X)
+#define elf_read_implies_exec(ex, compat_mode) \
+ (compat_mode == COMPAT_GNU_COMPAT)

struct task_struct;

diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 21ac5ee..1d9c4d8 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -572,6 +572,7 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
unsigned long start_code, end_code, start_data, end_data;
unsigned long reloc_func_desc __maybe_unused = 0;
int executable_stack = EXSTACK_DEFAULT;
+ int compat_mode = COMPAT_DEFAULT;
unsigned long def_flags = 0;
struct {
struct elfhdr elf_ex;
@@ -694,6 +695,9 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
executable_stack = EXSTACK_DISABLE_X;
break;
}
+ else if (elf_ppnt->p_type == PT_GNU_COMPAT)
+ if (elf_ppnt->p_flags & PF_X)
+ compat_mode = COMPAT_GNU_COMPAT;

/* Some simple consistency checks for the interpreter */
if (elf_interpreter) {
@@ -718,7 +722,7 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
/* Do this immediately, since STACK_TOP as used in setup_arg_pages
may depend on the personality. */
SET_PERSONALITY(loc->elf_ex);
- if (elf_read_implies_exec(loc->elf_ex, executable_stack))
+ if (elf_read_implies_exec(loc->elf_ex, compat_mode))
current->personality |= READ_IMPLIES_EXEC;

if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index fd88a39..1366919 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -123,6 +123,15 @@ extern int suid_dumpable;
#define EXSTACK_DISABLE_X 1 /* Disable executable stacks */
#define EXSTACK_ENABLE_X 2 /* Enable executable stacks */

+/* Compatibility modes */
+#define COMPAT_DEFAULT 0 /* Default compatibility mode */
+/* The value of COMPAT_GNU_COMPAT is set to the same as the value of
+ * EXSTACK_ENABLE_X for architectures that still expect the value of
+ * the executable_stack variable (although not recommended)
+ * in elf_read_implies_exec().
+ */
+#define COMPAT_GNU_COMPAT 2 /* GNU compatibility mode */
+
extern int setup_arg_pages(struct linux_binprm * bprm,
unsigned long stack_top,
int executable_stack);
diff --git a/include/linux/elf.h b/include/linux/elf.h
index 31f0508..a336740 100644
--- a/include/linux/elf.h
+++ b/include/linux/elf.h
@@ -13,7 +13,7 @@ struct file;
/* Executables for which elf_read_implies_exec() returns TRUE will
have the READ_IMPLIES_EXEC personality flag set automatically.
Override in asm/elf.h as needed. */
-# define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
+# define elf_read_implies_exec(ex, compat_mode) 0
#endif

/* 32-bit ELF base types. */
@@ -49,6 +49,7 @@ typedef __s64 Elf64_Sxword;
#define PT_GNU_EH_FRAME 0x6474e550

#define PT_GNU_STACK (PT_LOOS + 0x474e551)
+#define PT_GNU_COMPAT (PT_LOOS + 0x474e553)

/*
* Extended Numbering
\
 
 \ /
  Last update: 2012-01-09 12:57    [from the cache]
©2003-2014 Jasper Spaans. Advertise on this site