[gdb/testsuite] Update gdb.arch/i386-mpx-call.exp for -m32
authorTom de Vries <tdevries@suse.de>
Fri, 11 Dec 2020 17:26:40 +0000 (18:26 +0100)
committerTom de Vries <tdevries@suse.de>
Fri, 11 Dec 2020 17:26:40 +0000 (18:26 +0100)
When running test-case gdb.arch/i386-mpx-call.exp with target board unix/-m32,
we run into:
...
(gdb) continue^M
Continuing.^M
(gdb) FAIL: gdb.arch/i386-mpx-call.exp: upper_bnd0: continue to a bnd violation
...

Let's look first for reference at -m64, where the test passes.

The test-case uses -mmpx -fcheck-pointer-bounds to generate pointer checks in
the exec.  Effectively, -fcheck-pointer-bounds modifies the calling ABI: a
call passes pointer bounds as well as arguments.  The call to upper (with
four pointer arguments and an int argument, passed in 5 registers) is modified
like this:
...
   lea    -0xa0(%rbp),%rcx
   lea    -0x80(%rbp),%rdx
   lea    -0x60(%rbp),%rsi
   lea    -0x40(%rbp),%rax
   mov    $0x0,%r8d
+  bndmov -0x110(%rbp),%bnd3
+  bndmov -0x100(%rbp),%bnd2
+  bndmov -0xf0(%rbp),%bnd1
+  bndmov -0xe0(%rbp),%bnd0
   mov    %rax,%rdi
-  callq  <upper>
+  bnd callq <upper>
...
passsing the four pointer bounds in bounds registers BND0-3.

The top-level mechanism of the test is as follows:
- run the exec to after all mallocs are done, such that all pointer variables
  are valid
- do inferior calls, similar to those present in the program

The inferior call mechanism doesn't differentiate between a call to a function
compiled with -fcheck-pointer-bounds, and one without.  It merely resets the
bound registers to all-allowed state (see amd64_push_dummy_call), to make sure
the checks don't trigger during the inferior call.  [ This is the same as what
happens when executing a call without bnd prefix when the BNDPRESERVE bit of
the BNDCFG register is set to 0, a provision for calling an instrumented
function using a non-instrumented call. ]

First, two inferior calls are done (default_run and verify_default_values)
with the bound registers unmodified by the test.  So, the memory accesses are
performed with the bounds registers set by amd64_push_dummy_call to
all-allowed, and the bounds checks do not trigger.

Then we try to do an inferior call with modified bounds registers, set to
none-allowed.  In order to do that, we set a breakpoint at *upper before
doing the inferior call.  Once we hit the breakpoint during the inferior call,
the bounds registers are set to none-allowed, and we continue expecting to run
into an triggered bounds check, which takes the shape of a sigsegv.

Back to -m32.  Here, the pointer arguments are passed in memory rather than
registers, so with -fcheck-pointer-bounds, the pointer bounds are placed in
the Bounds Table using bndstx:
...
  movl   $0x0,0x10(%eax)
  lea    -0x70(%ebp),%edx
  mov    %edx,0xc(%eax)
  lea    -0x5c(%ebp),%edx
  mov    %edx,0x8(%eax)
  lea    -0x48(%ebp),%edx
  mov    %edx,0x4(%eax)
  lea    -0x34(%ebp),%edx
  mov    %edx,(%eax)
  lea    0xc(%eax),%edx
  mov    0xc(%eax),%ecx
  bndmov -0xa8(%ebp),%bnd1
  bndstx %bnd1,(%edx,%ecx,1)
  lea    0x8(%eax),%edx
  mov    0x8(%eax),%ecx
  bndmov -0xa0(%ebp),%bnd3
  bndstx %bnd3,(%edx,%ecx,1)
  lea    0x4(%eax),%edx
  mov    0x4(%eax),%ecx
  bndmov -0x98(%ebp),%bnd1
  bndstx %bnd1,(%edx,%ecx,1)
  mov    (%eax),%edx
  bndmov -0x90(%ebp),%bnd3
  bndstx %bnd3,(%eax,%edx,1)
  bnd call 804893f <upper>
...

Again, the bounds registers are reset at the start of the inferior call by
amd64_push_dummy_call, and modified by the test-case, but neither has any
effect.  The code in upper reads the pointer bounds from the Bounds Table, not
from the bounds registers.

Note that for a test.c with an out-of-bounds access:
...
$ cat test.c
void foo (int *a) { volatile int v = a[1]; }
int main (void) { int a; foo (&a); return 0; }
$ gcc test.c -mmpx -fcheck-pointer-bounds -g -m32
$ ./a.out
Saw a #BR! status 1 at 0x804848d
...
and inferior call foo (&a) right before "bnd call foo" (at the point that the
bounds for a are setup in the bounds table) doesn't trigger a bounds violation:
...
(gdb) call foo (&a)
(gdb)
...
This is because the bounds table doesn't associate a pointer with bounds, but
rather a pair of pointer and pointer location.  So, the bound is setup for &a,
with as location the pushed argument in the frame.  The inferior call however
executes in a dummy frame, so the bound is checked for &a with as location the
pushed argument in the dummy frame, which is different, so the bounds check
doesn't trigger.

In conclusion, this is expected behaviour.

Update the test-case to not expect to override effective pointer bounds using
the bounds registers when the bounds passing is done via the Bounds Table.

Tested on x86_64-linux.

gdb/testsuite/ChangeLog:

2020-12-11  Tom de Vries  <tdevries@suse.de>

PR testsuite/26991
* gdb.arch/i386-mpx-call.exp: Don't expect to trigger bounds
        violations by setting bounds registers if the bounds are passed in the
        Bounds Table.

gdb/testsuite/ChangeLog
gdb/testsuite/gdb.arch/i386-mpx-call.exp

index 5b89bcd5d85b4829291fe4ba251c8b65c0e27374..7f682281660619b7ebfcb292ea990ef970f58ed6 100644 (file)
@@ -1,3 +1,10 @@
+2020-12-11  Tom de Vries  <tdevries@suse.de>
+
+       PR testsuite/26991
+       * gdb.arch/i386-mpx-call.exp: Don't expect to trigger bounds
+        violations by setting bounds registers if the bounds are passed in the
+        Bounds Table.
+
 2020-12-11  Tom de Vries  <tdevries@suse.de>
 
        PR testsuite/26954
index 41abbeebcf95ebdec124594136ef361d8d013dda..8f8fea5415b8b408ca20efb7041c28be19c1f3d6 100644 (file)
@@ -49,6 +49,15 @@ gdb_test_multiple "print have_mpx ()" $test {
     }
 }
 
+set bounds_table 0
+gdb_test_multiple "disassemble upper" "" {
+    -re -wrap "bndldx.*" {
+       set bounds_table 1
+    }
+    -re -wrap "" {
+    }
+}
+
 # Convenience for returning from an inferior call that causes a BND violation.
 #
 gdb_test_no_output "set confirm off"
@@ -136,9 +145,25 @@ proc perform_a_call {func} {
 #
 proc check_bound_violation {parm parm_type is_positive} {
 
-    global u_fault
-
-    gdb_test "continue" "$u_fault.*" "continue to a bnd violation"
+    global u_fault bounds_table
+
+    set have_bnd_violation 0
+    gdb_test_multiple "continue" "continue to a bnd violation" {
+       -re -wrap "Continuing\." {
+           if { $bounds_table } {
+               pass $gdb_test_name
+           } else {
+               fail $gdb_test_name
+           }
+       }
+       -re -wrap "$u_fault.*" {
+           pass $gdb_test_name
+           set have_bnd_violation 1
+       }
+    }
+    if { ! $have_bnd_violation } {
+       return
+    }
 
     set message "access only one position"
     if {$is_positive == 1} {