RFC: PR 30907: BFD linker option to allow read-only data in code segments

Message ID 87ed9c3al6.fsf@redhat.com
State New
Headers
Series RFC: PR 30907: BFD linker option to allow read-only data in code segments |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Testing passed

Commit Message

Nick Clifton June 4, 2024, 5:02 p.m. UTC
  Hi Guys,

  Attached is an experimental patch to add new option the bfd linker:
  -z rodata-in-code.

  If used the option allows read only data to be placed into a code
  segment.  This is only effective if the -z separate-code option is in
  effect.  When used it has the ability to reduce the number of loadable
  segments from 4 to 3, which can have a big effect on the overall size
  of an executable.  (See PR 30907 for more discussion on this).

  Here is an example:

  $ cat test.c
      extern int printf (const char *, ...);
      int i = 42;
      const int * j = & i;
      int main (void) { return printf ("hello world %d\n", * j); }

  $ gcc test.c -Wl,-z,separate-code
  $ readelf -Wl a.out
      [...]
      LOAD     0x000000 0x0000000000400000 0x0000000000400000 0x000510 0x000510 R   0x1000
      LOAD     0x001000 0x0000000000401000 0x0000000000401000 0x000155 0x000155 R E 0x1000
      LOAD     0x002000 0x0000000000402000 0x0000000000402000 0x0000dc 0x0000dc R   0x1000
      LOAD     0x002df8 0x0000000000403df8 0x0000000000403df8 0x000228 0x000230 RW  0x1000
      [...]
      Section to Segment mapping:
      [...]
      02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
      03     .init .plt .text .fini 
      04     .rodata .eh_frame_hdr .eh_frame 
      05     .init_array .fini_array .dynamic .got .got.plt .data .bss 

  $ gcc test.c -Wl,-z,separate-code -Wl,-z,rodata-in-code
  $ readelf -Wl a.out
      [...]
      LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0004d8 0x0004d8 R   0x1000
      LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x0010dc 0x0010dc R E 0x1000
      LOAD           0x002df8 0x0000000000403df8 0x0000000000403df8 0x000228 0x000230 RW  0x1000
      [...]
      Section to Segment mapping:
      [...]
      02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
      03     .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
      04     .init_array .fini_array .dynamic .got .got.plt .data .bss 

  The patch is incomplete.  It does not have any documentation or test
  cases yet.  But I wanted to see if the general idea was going to cause
  concern for people, and to see if I had missed anything obvious.

  Thoughts, suggestions ?

Cheers
  Nick
  

Comments

Palmer Dabbelt June 4, 2024, 5:13 p.m. UTC | #1
On Tue, 04 Jun 2024 10:02:29 PDT (-0700), Nick Clifton wrote:
> Hi Guys,
>
>   Attached is an experimental patch to add new option the bfd linker:
>   -z rodata-in-code.
>
>   If used the option allows read only data to be placed into a code
>   segment.  This is only effective if the -z separate-code option is in
>   effect.  When used it has the ability to reduce the number of loadable
>   segments from 4 to 3, which can have a big effect on the overall size
>   of an executable.  (See PR 30907 for more discussion on this).
>
>   Here is an example:
>
>   $ cat test.c
>       extern int printf (const char *, ...);
>       int i = 42;
>       const int * j = & i;
>       int main (void) { return printf ("hello world %d\n", * j); }
>
>   $ gcc test.c -Wl,-z,separate-code
>   $ readelf -Wl a.out
>       [...]
>       LOAD     0x000000 0x0000000000400000 0x0000000000400000 0x000510 0x000510 R   0x1000
>       LOAD     0x001000 0x0000000000401000 0x0000000000401000 0x000155 0x000155 R E 0x1000
>       LOAD     0x002000 0x0000000000402000 0x0000000000402000 0x0000dc 0x0000dc R   0x1000
>       LOAD     0x002df8 0x0000000000403df8 0x0000000000403df8 0x000228 0x000230 RW  0x1000
>       [...]
>       Section to Segment mapping:
>       [...]
>       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
>       03     .init .plt .text .fini
>       04     .rodata .eh_frame_hdr .eh_frame
>       05     .init_array .fini_array .dynamic .got .got.plt .data .bss
>
>   $ gcc test.c -Wl,-z,separate-code -Wl,-z,rodata-in-code
>   $ readelf -Wl a.out
>       [...]
>       LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0004d8 0x0004d8 R   0x1000
>       LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x0010dc 0x0010dc R E 0x1000
>       LOAD           0x002df8 0x0000000000403df8 0x0000000000403df8 0x000228 0x000230 RW  0x1000
>       [...]
>       Section to Segment mapping:
>       [...]
>       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
>       03     .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
>       04     .init_array .fini_array .dynamic .got .got.plt .data .bss
>
>   The patch is incomplete.  It does not have any documentation or test
>   cases yet.  But I wanted to see if the general idea was going to cause
>   concern for people, and to see if I had missed anything obvious.
>
>   Thoughts, suggestions ?

I'd be slightly worried we mangle things with the mapping symbols in 
RISC-V land (and I suppose Arm has something similar).  I don't think 
there's any fundamental reason that can't be made to work, we'd just 
need to give it some testing.

Aside from that it seems like generally a useful concept.  In 
microcontroller land we spent a lot of time trying to make sure rodata 
doesn't eat writable memory.  Trying to juggle .rodata and .text mapping 
to the same flash can be clunky as it requires linker scripts and users 
generally don't understand those, so having an option seems reasonable 
to me.

>
> Cheers
>   Nick
  
Jan Beulich June 4, 2024, 5:14 p.m. UTC | #2
On 04.06.2024 19:02, Nick Clifton wrote:
> Hi Guys,
> 
>   Attached is an experimental patch to add new option the bfd linker:
>   -z rodata-in-code.
> 
>   If used the option allows read only data to be placed into a code
>   segment.  This is only effective if the -z separate-code option is in
>   effect.  When used it has the ability to reduce the number of loadable
>   segments from 4 to 3, which can have a big effect on the overall size
>   of an executable.  (See PR 30907 for more discussion on this).

While technically this looks okay, isn't this subverting the purpose of
-z separate-code then, reading that one's documentation? You apparently
retain the "wholly disjoint pages" aspect as per ...

>   Here is an example:
> 
>   $ cat test.c
>       extern int printf (const char *, ...);
>       int i = 42;
>       const int * j = & i;
>       int main (void) { return printf ("hello world %d\n", * j); }
> 
>   $ gcc test.c -Wl,-z,separate-code
>   $ readelf -Wl a.out
>       [...]
>       LOAD     0x000000 0x0000000000400000 0x0000000000400000 0x000510 0x000510 R   0x1000
>       LOAD     0x001000 0x0000000000401000 0x0000000000401000 0x000155 0x000155 R E 0x1000
>       LOAD     0x002000 0x0000000000402000 0x0000000000402000 0x0000dc 0x0000dc R   0x1000
>       LOAD     0x002df8 0x0000000000403df8 0x0000000000403df8 0x000228 0x000230 RW  0x1000
>       [...]
>       Section to Segment mapping:
>       [...]
>       02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
>       03     .init .plt .text .fini 
>       04     .rodata .eh_frame_hdr .eh_frame 
>       05     .init_array .fini_array .dynamic .got .got.plt .data .bss 
> 
>   $ gcc test.c -Wl,-z,separate-code -Wl,-z,rodata-in-code
>   $ readelf -Wl a.out
>       [...]
>       LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0004d8 0x0004d8 R   0x1000
>       LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x0010dc 0x0010dc R E 0x1000
>       LOAD           0x002df8 0x0000000000403df8 0x0000000000403df8 0x000228 0x000230 RW  0x1000

... this, yet with the segment being readable and executable it's
not clear to me whether that then actually gains us anything.

Jan
  
Jakub Jelinek June 4, 2024, 5:32 p.m. UTC | #3
On Tue, Jun 04, 2024 at 06:02:29PM +0100, Nick Clifton wrote:
>   Attached is an experimental patch to add new option the bfd linker:
>   -z rodata-in-code.
> 
>   If used the option allows read only data to be placed into a code
>   segment.  This is only effective if the -z separate-code option is in
>   effect.  When used it has the ability to reduce the number of loadable
>   segments from 4 to 3, which can have a big effect on the overall size
>   of an executable.  (See PR 30907 for more discussion on this).

Isn't that a security risk though?  One can then look for the ROP gadgets
also in .rodata/.eh_frame/.eh_frame_hdr sections.  I don't know if there
are more ROP gadgets in the sections before .init (the first PT_LOAD) or
in .rodata/.eh_frame*/.gcc_except_table/.gnu_extab sections.

I was wondering whether for -z separate-code it wouldn't be possible to
just remove the
  . = ALIGN(CONSTANT (MAXPAGESIZE));
  /* Adjust the address for the rodata segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));
part from the built-in linker script and move the
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .eh_frame_hdr   : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
  .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges*) }
part from where it is now to before
  . = ALIGN(CONSTANT (MAXPAGESIZE));
  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  }
to achieve the same effect (just 3 PT_LOAD segments instead of 4) without
sacrificing security.
On x86_64/i?86, I really don't see anything that make it a problem, if the
writable data sections refer to .rodata, they do it with full pointers,
and if .text refers to .rodata, it is still usually signed 32-bit immediate
which doesn't care if it is before or after .text, in the latter case there
is the limit of the first .text reference to latest .rodata symbol distance,
in the first case the limit is between first .rodata symbol and last .text
reference to it distance.
Maybe it could be a problem in -fsection-anchors defaulting arches if the
section anchor refers to both .rodata and .data symbols and a big gap in
between (the .text section) might be a problem.
Another problem might be arches which do use .sdata2/.sbss2.

Anyway, moving the
  ${WRITABLE_RODATA-${RODATA}}
  .${RODATA_NAME}1      ${RELOCATING-0} : { *(.${RODATA_NAME}1) }
  ${CREATE_SHLIB-${SDATA2}}
  ${CREATE_SHLIB-${SBSS2}}
  ${OTHER_READONLY_SECTIONS}
  .eh_frame_hdr ${RELOCATING-0} : { *(.eh_frame_hdr)${RELOCATING+ *(.eh_frame_entry .eh_frame_entry.*)} }
  .eh_frame     ${RELOCATING-0} : ONLY_IF_RO { KEEP (*(.eh_frame))${RELOCATING+ *(.eh_frame.*)} }
  .sframe       ${RELOCATING-0} : ONLY_IF_RO { *(.sframe)${RELOCATING+ *(.sframe.*)} }
  .gcc_except_table ${RELOCATING-0} : ONLY_IF_RO { *(.gcc_except_table${RELOCATING+ .gcc_except_table.*}) }
  .gnu_extab ${RELOCATING-0} : ONLY_IF_RO { *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges ${RELOCATING-0} : ONLY_IF_RO { *(.exception_ranges${RELOCATING+*}) }
  ${TEXT_PLT+${PLT_NEXT_DATA+${PLT} ${OTHER_PLT_SECTIONS}}}
part into a separate emit_rodata function and emitting it before .init for
if test -n "${SEPARATE_CODE}${SEPARATE_TEXT}"; then
and maybe 
  if test -z "${RODATA_ADDR}"; then
(dunno about SHLIB_RODATA_ADDR) and only emit the
SEGMENT_START("rodata-segment"
etc. related stuff if it isn't early, then trying to build a lot of code on
multiple arches (e.g. Fedora mass-prebuild) and see where it works and where
it doesn't.

	Jakub
  
Fangrui Song June 5, 2024, 2:21 a.m. UTC | #4
On 2024-06-04, Jakub Jelinek wrote:
>On Tue, Jun 04, 2024 at 06:02:29PM +0100, Nick Clifton wrote:
>>   Attached is an experimental patch to add new option the bfd linker:
>>   -z rodata-in-code.
>>
>>   If used the option allows read only data to be placed into a code
>>   segment.  This is only effective if the -z separate-code option is in
>>   effect.  When used it has the ability to reduce the number of loadable
>>   segments from 4 to 3, which can have a big effect on the overall size
>>   of an executable.  (See PR 30907 for more discussion on this).
>
>Isn't that a security risk though?  One can then look for the ROP gadgets
>also in .rodata/.eh_frame/.eh_frame_hdr sections.  I don't know if there
>are more ROP gadgets in the sections before .init (the first PT_LOAD) or
>in .rodata/.eh_frame*/.gcc_except_table/.gnu_extab sections.
>
>I was wondering whether for -z separate-code it wouldn't be possible to
>just remove the
>  . = ALIGN(CONSTANT (MAXPAGESIZE));
>  /* Adjust the address for the rodata segment.  We want to adjust up to
>     the same address within the page on the next page up.  */
>  . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));
>part from the built-in linker script and move the
>  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
>  .rodata1        : { *(.rodata1) }
>  .eh_frame_hdr   : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
>  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
>  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
>  .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
>  /* These sections are generated by the Sun/Oracle C++ compiler.  */
>  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges*) }
>part from where it is now to before
>  . = ALIGN(CONSTANT (MAXPAGESIZE));
>  .init           :
>  {
>    KEEP (*(SORT_NONE(.init)))
>  }
>to achieve the same effect (just 3 PT_LOAD segments instead of 4) without
>sacrificing security.
>On x86_64/i?86, I really don't see anything that make it a problem, if the
>writable data sections refer to .rodata, they do it with full pointers,
>and if .text refers to .rodata, it is still usually signed 32-bit immediate
>which doesn't care if it is before or after .text, in the latter case there
>is the limit of the first .text reference to latest .rodata symbol distance,
>in the first case the limit is between first .rodata symbol and last .text
>reference to it distance.
>Maybe it could be a problem in -fsection-anchors defaulting arches if the
>section anchor refers to both .rodata and .data symbols and a big gap in
>between (the .text section) might be a problem.
>Another problem might be arches which do use .sdata2/.sbss2.
>
>Anyway, moving the
>  ${WRITABLE_RODATA-${RODATA}}
>  .${RODATA_NAME}1      ${RELOCATING-0} : { *(.${RODATA_NAME}1) }
>  ${CREATE_SHLIB-${SDATA2}}
>  ${CREATE_SHLIB-${SBSS2}}
>  ${OTHER_READONLY_SECTIONS}
>  .eh_frame_hdr ${RELOCATING-0} : { *(.eh_frame_hdr)${RELOCATING+ *(.eh_frame_entry .eh_frame_entry.*)} }
>  .eh_frame     ${RELOCATING-0} : ONLY_IF_RO { KEEP (*(.eh_frame))${RELOCATING+ *(.eh_frame.*)} }
>  .sframe       ${RELOCATING-0} : ONLY_IF_RO { *(.sframe)${RELOCATING+ *(.sframe.*)} }
>  .gcc_except_table ${RELOCATING-0} : ONLY_IF_RO { *(.gcc_except_table${RELOCATING+ .gcc_except_table.*}) }
>  .gnu_extab ${RELOCATING-0} : ONLY_IF_RO { *(.gnu_extab*) }
>  /* These sections are generated by the Sun/Oracle C++ compiler.  */
>  .exception_ranges ${RELOCATING-0} : ONLY_IF_RO { *(.exception_ranges${RELOCATING+*}) }
>  ${TEXT_PLT+${PLT_NEXT_DATA+${PLT} ${OTHER_PLT_SECTIONS}}}
>part into a separate emit_rodata function and emitting it before .init for
>if test -n "${SEPARATE_CODE}${SEPARATE_TEXT}"; then
>and maybe
>  if test -z "${RODATA_ADDR}"; then
>(dunno about SHLIB_RODATA_ADDR) and only emit the
>SEGMENT_START("rodata-segment"
>etc. related stuff if it isn't early, then trying to build a lot of code on
>multiple arches (e.g. Fedora mass-prebuild) and see where it works and where
>it doesn't.
>
>	Jakub
>

(I am not familiar with the code. Hopefully I do not misunderstand
Jakub's description.)
I agree that placing .rodata/.eh_frame after .dynsym/.rela.dyn is a more
optimal layout. This would indeed involve adjusting the internal linker script.

Additionally, ld/ldlang.c:lang_output_section_find_by_flags might need a change,
as it places .rodata after .text .

My description of lld's section layout can be found at
https://maskray.me/blog/2020-11-15-explain-gnu-linker-options#no-rosegment
and
https://maskray.me/blog/2023-12-17-exploring-the-section-layout-in-linker-output

* R PT_LOAD
* RX PT_LOAD
* RW PT_LOAD (overlaps with PT_GNU_RELRO)
* RW PT_LOAD

> Specify this option to combine the R PT_LOAD and the RX PT_LOAD. The RX
> PT_LOAD segment is traditionally called the text segment and is the
> first segment.
>
> ld.lld places rodata and data on both sides of text. This layout has
> the advantage that the distance between text and data is shorter,
> decreasing the relocation overflow pressure.

Since GNU ld has placed .text before .rodata for a very long time and some
programs may rely on this, I agree that an option to place .rodata before .text
should initially be opt-in.
This could be revisited in the future as lld's layout (also adopted by mold) has
been thoroughly tested by many distributions.

---

**Proposed Changes for GNU ld:**

* Split -z separate-code into --rosegment and -z separate-code (as suggested in https://sourceware.org/bugzilla/show_bug.cgi?id=30907#c4)
* Restore the pre-2.31 -z noseparate-code default for Linux x86, prioritizing file size concerns.
* Add an option to place read-only sections entirely before .text, eliminating the R segment after the RX segment.

Regarding security concerns:

>Isn't that a security risk though?  One can then look for the ROP gadgets
>also in .rodata/.eh_frame/.eh_frame_hdr sections.  I don't know if there
>are more ROP gadgets in the sections before .init (the first PT_LOAD) or
>in .rodata/.eh_frame*/.gcc_except_table/.gnu_extab sections.

While -z separate-code might be seen as a security measure, executable memory
inherently contains many ROP gadgets. I believe using -z separate-code for
security purposes is really a security theatre. (See also
https://isopenbsdsecu.re/mitigations/rop_removal/)

A potentially more useful feature for security is execute-only code supported
by AArch64 (ld.lld --execute-only), which makes the PF_R|PF_X PT_LOAD segment
PF_X only.

The possibly useful feature is ld.lld --execute-only, which makes the
PF_R|PF_X PT_LOAD segment PF_X only. lld --execute-only is incompatible with --no-rosegment:

% myld.lld -Ttext=0xcafe0000 a.o -o a.so -shared --execute-only --no-rosegment
ld.lld: error: --execute-only and --no-rosegment cannot be used together
  
Jakub Jelinek June 5, 2024, 8:26 a.m. UTC | #5
On Tue, Jun 04, 2024 at 07:21:36PM -0700, Fangrui Song wrote:
> (I am not familiar with the code. Hopefully I do not misunderstand
> Jakub's description.)

That is just a shell script that creates the default linker script.

> I agree that placing .rodata/.eh_frame after .dynsym/.rela.dyn is a more
> optimal layout. This would indeed involve adjusting the internal linker script.

And yet another option perhaps for architectures where the move
.rodata/.eh_frame etc. before .init/.text doesn't work for various reasons
(constraints on relocations etc.) could be move the read-only sections from before
.init to after .fini and before .rodata.
Obviously the ELF headers and program headers and likely
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
  [ 2] .note.gnu.property NOTE            0000000000000338 000338 000050 00   A  0   0  8
  [ 3] .note.gnu.build-id NOTE            0000000000000388 000388 000024 00   A  0   0  4
  [ 4] .note.package     NOTE            00000000000003ac 0003ac 000088 00   A  0   0  4
  [ 5] .note.ABI-tag     NOTE            0000000000000434 000434 000020 00   A  0   0  4
sections would need to stay where they are (as I think various loaders
assume the notes etc. are at the start) and because of that become
executable, but I don't see why
  [ 6] .gnu.hash         GNU_HASH        0000000000000458 000458 004bd8 00   A  7   0  8
  [ 7] .dynsym           DYNSYM          0000000000005030 005030 00ebf8 18   A  8   1  8
  [ 8] .dynstr           STRTAB          0000000000013c28 013c28 009cf9 00   A  0   0  1
  [ 9] .gnu.version      VERSYM          000000000001d922 01d922 0013aa 02   A  7   0  2
  [10] .gnu.version_r    VERNEED         000000000001ecd0 01ecd0 0000c0 00   A  8   1  8
  [11] .rela.dyn         RELA            000000000001ed90 01ed90 00dbc0 18   A  7   0  8
  [12] .rela.plt         RELA            000000000002c950 02c950 001548 18  AI  7  25  8
etc. couldn't move before .rodata.  In that configuration, it would be
RX PT_LOAD
R PT_LOAD
RW PT_LOAD (including PT_GNU_RELRO subsegment if any)

That said, if moving .rodat/.eh_frame before .init works on an architecture,
I think it is certainly better like that.

> Since GNU ld has placed .text before .rodata for a very long time and some
> programs may rely on this, I agree that an option to place .rodata before .text
> should initially be opt-in.

Sure, that is possible.

	Jakub
  
Jakub Jelinek June 5, 2024, 9:15 a.m. UTC | #6
On Wed, Jun 05, 2024 at 10:27:00AM +0200, Jakub Jelinek wrote:
> And yet another option perhaps for architectures where the move
> .rodata/.eh_frame etc. before .init/.text doesn't work for various reasons
> (constraints on relocations etc.) could be move the read-only sections from before
> .init to after .fini and before .rodata.
> Obviously the ELF headers and program headers and likely
>   [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
>   [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
>   [ 2] .note.gnu.property NOTE            0000000000000338 000338 000050 00   A  0   0  8
>   [ 3] .note.gnu.build-id NOTE            0000000000000388 000388 000024 00   A  0   0  4
>   [ 4] .note.package     NOTE            00000000000003ac 0003ac 000088 00   A  0   0  4
>   [ 5] .note.ABI-tag     NOTE            0000000000000434 000434 000020 00   A  0   0  4
> sections would need to stay where they are (as I think various loaders
> assume the notes etc. are at the start) and because of that become
> executable, but I don't see why
>   [ 6] .gnu.hash         GNU_HASH        0000000000000458 000458 004bd8 00   A  7   0  8
>   [ 7] .dynsym           DYNSYM          0000000000005030 005030 00ebf8 18   A  8   1  8
>   [ 8] .dynstr           STRTAB          0000000000013c28 013c28 009cf9 00   A  0   0  1
>   [ 9] .gnu.version      VERSYM          000000000001d922 01d922 0013aa 02   A  7   0  2
>   [10] .gnu.version_r    VERNEED         000000000001ecd0 01ecd0 0000c0 00   A  8   1  8
>   [11] .rela.dyn         RELA            000000000001ed90 01ed90 00dbc0 18   A  7   0  8
>   [12] .rela.plt         RELA            000000000002c950 02c950 001548 18  AI  7  25  8
> etc. couldn't move before .rodata.  In that configuration, it would be
> RX PT_LOAD
> R PT_LOAD
> RW PT_LOAD (including PT_GNU_RELRO subsegment if any)

Note, prelink used to treat
    case SHT_HASH:
    case SHT_GNU_HASH:
    case SHT_DYNSYM:
    case SHT_REL:
    case SHT_RELA:
    case SHT_STRTAB:
    case SHT_NOTE:
    case SHT_GNU_verdef:
    case SHT_GNU_verneed:
    case SHT_GNU_versym:
    case SHT_GNU_LIBLIST:
and .interp sections as movable and happily moved them somewhere else if it
needed to make up space for growth of some fixed position section, so the
only worry would be if there are constraints on the distances between .text
and .rodata sections or the .sdata2/.sbss2 etc. sections after it, but both
(as well as .eh_frame) sections can be many megabytes long already, so a few
KB in between shouldn't break stuff.

	Jakub
  

Patch

diff --git a/bfd/elf.c b/bfd/elf.c
index 74236a658fd..1414c973532 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -5443,6 +5443,14 @@  _bfd_elf_map_sections_to_segments (bfd *abfd,
 	    }
 	  else if (info != NULL
 		   && info->separate_code
+		   && info->rodata_in_code
+		   && executable != ((hdr->flags & (SEC_CODE | SEC_READONLY)) != 0))
+	    {
+	      new_segment = true;
+	    }
+	  else if (info != NULL
+		   && info->separate_code
+		   && ! info->rodata_in_code
 		   && executable != ((hdr->flags & SEC_CODE) != 0))
 	    {
 	      new_segment = true;
diff --git a/include/bfdlink.h b/include/bfdlink.h
index eac07d78364..2411b53485f 100644
--- a/include/bfdlink.h
+++ b/include/bfdlink.h
@@ -420,9 +420,12 @@  struct bfd_link_info
      relocations.  */
   unsigned int enable_dt_relr: 1;
 
-  /* TRUE if separate code segment should be created.  */
+  /* TRUE if a separate code segment should be created.  */
   unsigned int separate_code: 1;
 
+  /* TRUE if a read-only data is allowed in the code segment.  */
+  unsigned int rodata_in_code: 1;
+
   /* Nonzero if .eh_frame_hdr section and PT_GNU_EH_FRAME ELF segment
      should be created.  1 for DWARF2 tables, 2 for compact tables.  */
   unsigned int eh_frame_hdr_type: 2;
diff --git a/ld/emultempl/elf.em b/ld/emultempl/elf.em
index 55a870f7d2d..617581308ad 100644
--- a/ld/emultempl/elf.em
+++ b/ld/emultempl/elf.em
@@ -852,6 +852,10 @@  fragment <<EOF
 	link_info.separate_code = true;
       else if (strcmp (optarg, "noseparate-code") == 0)
 	link_info.separate_code = false;
+      else if (strcmp (optarg, "rodata-in-code") == 0)
+	link_info.rodata_in_code = true;
+      else if (strcmp (optarg, "no-rodata-in-code") == 0)
+	link_info.rodata_in_code = false;
       else if (strcmp (optarg, "common") == 0)
 	link_info.elf_stt_common = elf_stt_common;
       else if (strcmp (optarg, "nocommon") == 0)
diff --git a/ld/lexsup.c b/ld/lexsup.c
index 4125d849f2c..7b0710c2001 100644
--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -2234,6 +2234,10 @@  elf_shlib_list_options (FILE *file)
   fprintf (file, _("\
   -z noseparate-code          Don't create separate code program header (default)\n"));
 #endif
+  fprintf (file, _("\
+  -z rodata-in-code           If separate-code is active, allow read-only data in the code segment\n"));
+  fprintf (file, _("\
+  -z no-rodata-in-code        If separate-code is active place read-only data in its own segment (default)\n"));
   fprintf (file, _("\
   -z common                   Generate common symbols with STT_COMMON type\n"));
   fprintf (file, _("\