diff mbox series

[4/5] selftests/sgx: Ensure expected enclave data buffer size and placement.

Message ID 20230724165832.15797-5-jo.vanbulck@cs.kuleuven.be (mailing list archive)
State New, archived
Headers show
Series selftests/sgx: Fix compilation errors. | expand

Commit Message

Jo Van Bulck July 24, 2023, 4:58 p.m. UTC
Do not declare the enclave data buffer static to ensure it is not optimized
away by the compiler, even when not used entirely by the test enclave code.
Use -fPIE to make the compiler access the non-static buffer with
RIP-relative addressing. Place the enclave data buffer in a separate
section that is explicitly placed at the start of the .data segment in the
linker script, as expected by the external tests manipulating page
permissions.

Signed-off-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be>
---
 tools/testing/selftests/sgx/Makefile      | 2 +-
 tools/testing/selftests/sgx/test_encl.c   | 5 +++--
 tools/testing/selftests/sgx/test_encl.lds | 1 +
 3 files changed, 5 insertions(+), 3 deletions(-)

Comments

Jarkko Sakkinen July 28, 2023, 7:19 p.m. UTC | #1
On Mon Jul 24, 2023 at 4:58 PM UTC, Jo Van Bulck wrote:
> Do not declare the enclave data buffer static to ensure it is not optimized
                                         ~~~~~~
					 as static

/enclave data buffer/encl_buffer/

> away by the compiler, even when not used entirely by the test enclave code.

"Declare encl_buffer as global, in order to ensure that it is not
optimized away by the compiler."

So, when exactly is it optimized away by the compiler? This is missing.

BR, Jarkko
Huang, Kai Aug. 3, 2023, 4:22 a.m. UTC | #2
On Mon, 2023-07-24 at 18:58 +0200, Jo Van Bulck wrote:
> Do not declare the enclave data buffer static to ensure it is not optimized
> away by the compiler, even when not used entirely by the test enclave code.

The "encl_buffer" array is initialized to 1 explicitly, which means it should be
in .data section.  As Jarkko commented, can you add more information about in
what cases it can be optimized away?

> Use -fPIE to make the compiler access the non-static buffer with
> RIP-relative addressing. 
> 

I am not quite following how does "RIP-relative addressing" fix any problem?  Is
there any hard relationship between "RIP-relative addressing" and the problem
that you are having?

> Place the enclave data buffer in a separate
> section that is explicitly placed at the start of the .data segment in the
> linker script, as expected by the external tests manipulating page
> permissions.

So this change is not to fix the problem that "the compiler may optimize away
the encl_buffer array", correct?

> 
> Signed-off-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be>
> ---
>  tools/testing/selftests/sgx/Makefile      | 2 +-
>  tools/testing/selftests/sgx/test_encl.c   | 5 +++--
>  tools/testing/selftests/sgx/test_encl.lds | 1 +
>  3 files changed, 5 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/testing/selftests/sgx/Makefile b/tools/testing/selftests/sgx/Makefile
> index 50aab6b57da3..c5483445ba28 100644
> --- a/tools/testing/selftests/sgx/Makefile
> +++ b/tools/testing/selftests/sgx/Makefile
> @@ -13,7 +13,7 @@ endif
>  
>  INCLUDES := -I$(top_srcdir)/tools/include
>  HOST_CFLAGS := -Wall -Werror -g $(INCLUDES) -fPIC -z noexecstack
> -ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIC \
> +ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIE \
>  	       -fno-stack-protector -mrdrnd $(INCLUDES)
>  
>  TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx
> diff --git a/tools/testing/selftests/sgx/test_encl.c b/tools/testing/selftests/sgx/test_encl.c
> index aba301abefb8..5c274e517d13 100644
> --- a/tools/testing/selftests/sgx/test_encl.c
> +++ b/tools/testing/selftests/sgx/test_encl.c
> @@ -7,9 +7,10 @@
>  /*
>   * Data buffer spanning two pages that will be placed first in .data
>   * segment. Even if not used internally the second page is needed by
> - * external test manipulating page permissions.
> + * external test manipulating page permissions. Do not declare this
> + * buffer as static, so the compiler cannot optimize it out.
>   */
> -static uint8_t encl_buffer[8192] = { 1 };
> +uint8_t __attribute__((section(".data.encl_buffer"))) encl_buffer[8192];
>  
>  enum sgx_enclu_function {
>  	EACCEPT = 0x5,
> diff --git a/tools/testing/selftests/sgx/test_encl.lds b/tools/testing/selftests/sgx/test_encl.lds
> index ca659db2a534..79b1e41d8d24 100644
> --- a/tools/testing/selftests/sgx/test_encl.lds
> +++ b/tools/testing/selftests/sgx/test_encl.lds
> @@ -24,6 +24,7 @@ SECTIONS
>  	} : text
>  
>  	.data : {
> +		*(.data.encl_buffer)
>  		*(.data*)
>  	} : data
>
Jo Van Bulck Aug. 7, 2023, 9:41 a.m. UTC | #3
On 28.07.23 21:19, Jarkko Sakkinen wrote:
> So, when exactly is it optimized away by the compiler? This is missing.

The problem is that declaring encl_buf as static, implies that it will 
only be used in this file and the compiler is allowed to optimize away 
any entries that are never used within this compilation unit (e.g., when 
optimizing out the memcpy calls).

In reality, the tests outside test_encl.elf rely on both the size and 
exact placement of encl_buf at the start of .data.

For example, clang -Os generates the following (legal) code when 
encl_bug is declared as static:

0000000000001020 <do_encl_op_put_to_buf>:
     mov    0x8(%rdi),%al
     mov    %al,0x1fd7(%rip)   # 3000 <encl_buffer.0>
     mov    0x9(%rdi),%al
     mov    %al,0x8fce(%rip)   # a000 <encl_buffer.1.0>
     mov    0xa(%rdi),%al
     mov    %al,0x8fd5(%rip)   # a010 <encl_buffer.1.1>
     mov    0xb(%rdi),%al
     mov    %al,0x8fce(%rip)   # a012 <encl_buffer.1.2>
     mov    0xc(%rdi),%al
     mov    %al,0x8fd3(%rip)   # a020 <encl_buffer.1.3>
     mov    0xd(%rdi),%al
     mov    %al,0x8fce(%rip)   # a024 <encl_buffer.1.4>
     mov    0xe(%rdi),%al
     mov    %al,0x8fd1(%rip)   # a030 <encl_buffer.1.5>
     mov    0xf(%rdi),%al
     mov    %al,0x8fca(%rip)   # a032 <encl_buffer.1.6>
     ret

Disassembly of section .data:

0000000000003000 <encl_buffer.0>:
     3000:       01 00
         ...
0000000000004000 <encl_ssa_tcs1>:

Thus, this proposed patch fixes both the size and location:

1. removing the static keyword from the encl_bug declaration ensures 
that the _entire_ buffer is preserved with expected size, as the 
compiler cannot anymore assume encl_buf is only used in this file.

2. adding _attribute__((section(".data.encl_buffer"))) ensures that we 
can control the expected location at the start of the .data section. I 
think this is optional, as encl_buf always seems to be placed at the 
start of .data in all my tests. But afaik this is not guaranteed as per 
the C standard and such constraints on exact placement should better be 
explicitly controlled in the linker script(?)
Jo Van Bulck Aug. 7, 2023, 9:50 a.m. UTC | #4
On 03.08.23 06:22, Huang, Kai wrote:
> The "encl_buffer" array is initialized to 1 explicitly, which means it should be
> in .data section.  As Jarkko commented, can you add more information about in
> what cases it can be optimized away?

Yes it will indeed be in .data, but it may be reduced in size. See my 
reply to Jarkko's question for more information and a concrete example.

> I am not quite following how does "RIP-relative addressing" fix any problem?  Is
> there any hard relationship between "RIP-relative addressing" and the problem
> that you are having?

Good point. I think this is now cleared up with the -static-pie 
discussion in reply to you other point on patch 2/5.

Concretely, the reason -fPIE is needed is that otherwise global 
variables (i.e., not declared as static) would not be addressed 
RIP-relative, but as hard-coded addresses assuming the -static binary is 
loaded at a fixed address.
> So this change is not to fix the problem that "the compiler may optimize away
> the encl_buffer array", correct?

Correct, this fix ensures the expected location. As per my reply to 
Jarkko's question:

> 2. adding _attribute__((section(".data.encl_buffer"))) ensures that we can control the expected location at the start of the .data section. I think this is optional, as encl_buf always seems to be placed at the start of .data in all my tests. But afaik this is not guaranteed as per the C standard and such constraints on exact placement should better be explicitly controlled in the linker script(?) 

Perhaps I better split this into 2 separate patches?

The location-control may also not be needed in practice, but afaik that 
would be undefined C behavior (cf above) and better be avoided?

Best,
Jo


> 
>>
>> Signed-off-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be>
>> ---
>>   tools/testing/selftests/sgx/Makefile      | 2 +-
>>   tools/testing/selftests/sgx/test_encl.c   | 5 +++--
>>   tools/testing/selftests/sgx/test_encl.lds | 1 +
>>   3 files changed, 5 insertions(+), 3 deletions(-)
>>
>> diff --git a/tools/testing/selftests/sgx/Makefile b/tools/testing/selftests/sgx/Makefile
>> index 50aab6b57da3..c5483445ba28 100644
>> --- a/tools/testing/selftests/sgx/Makefile
>> +++ b/tools/testing/selftests/sgx/Makefile
>> @@ -13,7 +13,7 @@ endif
>>   
>>   INCLUDES := -I$(top_srcdir)/tools/include
>>   HOST_CFLAGS := -Wall -Werror -g $(INCLUDES) -fPIC -z noexecstack
>> -ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIC \
>> +ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIE \
>>   	       -fno-stack-protector -mrdrnd $(INCLUDES)
>>   
>>   TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx
>> diff --git a/tools/testing/selftests/sgx/test_encl.c b/tools/testing/selftests/sgx/test_encl.c
>> index aba301abefb8..5c274e517d13 100644
>> --- a/tools/testing/selftests/sgx/test_encl.c
>> +++ b/tools/testing/selftests/sgx/test_encl.c
>> @@ -7,9 +7,10 @@
>>   /*
>>    * Data buffer spanning two pages that will be placed first in .data
>>    * segment. Even if not used internally the second page is needed by
>> - * external test manipulating page permissions.
>> + * external test manipulating page permissions. Do not declare this
>> + * buffer as static, so the compiler cannot optimize it out.
>>    */
>> -static uint8_t encl_buffer[8192] = { 1 };
>> +uint8_t __attribute__((section(".data.encl_buffer"))) encl_buffer[8192];
>>   
>>   enum sgx_enclu_function {
>>   	EACCEPT = 0x5,
>> diff --git a/tools/testing/selftests/sgx/test_encl.lds b/tools/testing/selftests/sgx/test_encl.lds
>> index ca659db2a534..79b1e41d8d24 100644
>> --- a/tools/testing/selftests/sgx/test_encl.lds
>> +++ b/tools/testing/selftests/sgx/test_encl.lds
>> @@ -24,6 +24,7 @@ SECTIONS
>>   	} : text
>>   
>>   	.data : {
>> +		*(.data.encl_buffer)
>>   		*(.data*)
>>   	} : data
>>   
>
Huang, Kai Aug. 18, 2023, 1:07 p.m. UTC | #5
On Mon, 2023-08-07 at 11:41 +0200, Jo Van Bulck wrote:
> On 28.07.23 21:19, Jarkko Sakkinen wrote:
> > So, when exactly is it optimized away by the compiler? This is missing.
> 
> The problem is that declaring encl_buf as static, implies that it will 
> only be used in this file and the compiler is allowed to optimize away 
> any entries that are never used within this compilation unit (e.g., when 
> optimizing out the memcpy calls).
> 
> In reality, the tests outside test_encl.elf rely on both the size and 
> exact placement of encl_buf at the start of .data.
> 
> For example, clang -Os generates the following (legal) code when 
> encl_bug is declared as static:
> 
> 0000000000001020 <do_encl_op_put_to_buf>:
>      mov    0x8(%rdi),%al
>      mov    %al,0x1fd7(%rip)   # 3000 <encl_buffer.0>
>      mov    0x9(%rdi),%al
>      mov    %al,0x8fce(%rip)   # a000 <encl_buffer.1.0>
>      mov    0xa(%rdi),%al
>      mov    %al,0x8fd5(%rip)   # a010 <encl_buffer.1.1>
>      mov    0xb(%rdi),%al
>      mov    %al,0x8fce(%rip)   # a012 <encl_buffer.1.2>
>      mov    0xc(%rdi),%al
>      mov    %al,0x8fd3(%rip)   # a020 <encl_buffer.1.3>
>      mov    0xd(%rdi),%al
>      mov    %al,0x8fce(%rip)   # a024 <encl_buffer.1.4>
>      mov    0xe(%rdi),%al
>      mov    %al,0x8fd1(%rip)   # a030 <encl_buffer.1.5>
>      mov    0xf(%rdi),%al
>      mov    %al,0x8fca(%rip)   # a032 <encl_buffer.1.6>
>      ret
> 
> Disassembly of section .data:
> 
> 0000000000003000 <encl_buffer.0>:
>      3000:       01 00
>          ...
> 0000000000004000 <encl_ssa_tcs1>:
> 
> Thus, this proposed patch fixes both the size and location:
> 
> 1. removing the static keyword from the encl_bug declaration ensures 
> that the _entire_ buffer is preserved with expected size, as the 
> compiler cannot anymore assume encl_buf is only used in this file.

Could we use "used" attribute?

https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html

used 

	This attribute, attached to a variable with static storage, means that 
	the variable must be emitted even if it appears that the variable is 
	not referenced.

	When applied to a static data member of a C++ class template, the 
	attribute also means that the member is instantiated if the class 
	itself is instantiated.
> 
> 2. adding _attribute__((section(".data.encl_buffer"))) ensures that we 
> can control the expected location at the start of the .data section. I 
> think this is optional, as encl_buf always seems to be placed at the 
> start of .data in all my tests. But afaik this is not guaranteed as per 
> the C standard and such constraints on exact placement should better be 
> explicitly controlled in the linker script(?)

This looks sane.
Jo Van Bulck Aug. 19, 2023, 1:11 a.m. UTC | #6
On 18.08.23 06:07, Huang, Kai wrote:
> Could we use "used" attribute?
> 
> https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html
> 
> used
> 
> 	This attribute, attached to a variable with static storage, means that
> 	the variable must be emitted even if it appears that the variable is
> 	not referenced.
> 
> 	When applied to a static data member of a C++ class template, the
> 	attribute also means that the member is instantiated if the class
> 	itself is instantiated.

Thank you for pointing this out! I was not aware of this attribute, but 
it is indeed exactly what we need in this case and works as expected.

>>
>> 2. adding _attribute__((section(".data.encl_buffer"))) ensures that we
>> can control the expected location at the start of the .data section. I
>> think this is optional, as encl_buf always seems to be placed at the
>> start of .data in all my tests. But afaik this is not guaranteed as per
>> the C standard and such constraints on exact placement should better be
>> explicitly controlled in the linker script(?)
> 
> This looks sane.

Thanks, applying this in a separate commit as discussed.

Best,
Jo
diff mbox series

Patch

diff --git a/tools/testing/selftests/sgx/Makefile b/tools/testing/selftests/sgx/Makefile
index 50aab6b57da3..c5483445ba28 100644
--- a/tools/testing/selftests/sgx/Makefile
+++ b/tools/testing/selftests/sgx/Makefile
@@ -13,7 +13,7 @@  endif
 
 INCLUDES := -I$(top_srcdir)/tools/include
 HOST_CFLAGS := -Wall -Werror -g $(INCLUDES) -fPIC -z noexecstack
-ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIC \
+ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIE \
 	       -fno-stack-protector -mrdrnd $(INCLUDES)
 
 TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx
diff --git a/tools/testing/selftests/sgx/test_encl.c b/tools/testing/selftests/sgx/test_encl.c
index aba301abefb8..5c274e517d13 100644
--- a/tools/testing/selftests/sgx/test_encl.c
+++ b/tools/testing/selftests/sgx/test_encl.c
@@ -7,9 +7,10 @@ 
 /*
  * Data buffer spanning two pages that will be placed first in .data
  * segment. Even if not used internally the second page is needed by
- * external test manipulating page permissions.
+ * external test manipulating page permissions. Do not declare this
+ * buffer as static, so the compiler cannot optimize it out.
  */
-static uint8_t encl_buffer[8192] = { 1 };
+uint8_t __attribute__((section(".data.encl_buffer"))) encl_buffer[8192];
 
 enum sgx_enclu_function {
 	EACCEPT = 0x5,
diff --git a/tools/testing/selftests/sgx/test_encl.lds b/tools/testing/selftests/sgx/test_encl.lds
index ca659db2a534..79b1e41d8d24 100644
--- a/tools/testing/selftests/sgx/test_encl.lds
+++ b/tools/testing/selftests/sgx/test_encl.lds
@@ -24,6 +24,7 @@  SECTIONS
 	} : text
 
 	.data : {
+		*(.data.encl_buffer)
 		*(.data*)
 	} : data