Fix bugs in the x86-64 and x32 target (#887) (#889)

This commit fixes two bugs in ffi in the x86-64 target. The bugs were
introduced by the commit d21881f55ed4a44d464c9091871e69b0bb47611a ("Fix
x86/ffi64 calls with 6 gp and some sse registers").

The first bug is that when we pass an argument with less than 8 bytes,
ffi will read memory beyond argument end, causing a crash if the argument
is located just before the end of the mapped region.

The second bug is in the x32 ABI - pointers in x32 are 4-byte, but GCC
assumes that the pointer values in the registers are zero-extended. ffi
doesn't respect this assumption, causing crashes in the called library.

For example, when we compile this function for x32:
int fn(int *a)
{
	if (a)
		return *a;
	return -1;
}
we get this code:
fn:
	testq   %rdi, %rdi
	je      .L3
	movl    (%edi), %eax
	ret
.L3:
	movl    $-1, %eax
	ret
When we call this function using ffi with the argument NULL, the function
crashes because top 4 bytes of the RDI register are not cleared.


Fixes: d21881f55ed4 ("Fix x86/ffi64 calls with 6 gp and some sse registers (#848)")

Signed-off-by: Mikulas Patocka <mikulas@twibright.com>
This commit is contained in:
mikulas-patocka 2025-03-27 01:31:49 +01:00 committed by GitHub
parent cf69efabca
commit fe203ffbb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 1 deletions

View File

@ -654,7 +654,7 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
break;
default:
reg_args->gpr[gprcount] = 0;
memcpy (&reg_args->gpr[gprcount], a, sizeof(UINT64));
memcpy (&reg_args->gpr[gprcount], a, size <= 8 ? size : 8);
}
gprcount++;
break;

View File

@ -0,0 +1,54 @@
/* Area: ffi_call
Purpose: Tests if ffi_call reads data beyond end.
Limitations: needs mmap.
PR: 887
Originator: Mikulas Patocka <mikulas@twibright.com> */
/* { dg-do run } */
#include "ffitest.h"
#ifdef __linux__
#include <sys/mman.h>
#include <unistd.h>
static int fn(unsigned char a, unsigned short b, unsigned int c, unsigned long d)
{
return (int)(a + b + c + d);
}
#endif
int main(void)
{
#ifdef __linux__
ffi_cif cif;
ffi_type *args[MAX_ARGS];
void *values[MAX_ARGS];
ffi_arg rint;
char *m;
int ps;
args[0] = &ffi_type_uchar;
args[1] = &ffi_type_ushort;
args[2] = &ffi_type_uint;
args[3] = &ffi_type_ulong;
CHECK(ffi_prep_cif(&cif, ABI_NUM, 4, &ffi_type_sint, args) == FFI_OK);
ps = getpagesize();
m = mmap(NULL, ps * 3, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
CHECK(m != MAP_FAILED);
CHECK(mprotect(m, ps, PROT_NONE) == 0);
CHECK(mprotect(m + ps * 2, ps, PROT_NONE) == 0);
values[0] = m + ps * 2 - sizeof(unsigned char);
values[1] = m + ps * 2 - sizeof(unsigned short);
values[2] = m + ps * 2 - sizeof(unsigned int);
values[3] = m + ps * 2 - sizeof(unsigned long);
ffi_call(&cif, FFI_FN(fn), &rint, values);
CHECK((int)rint == 0);
values[0] = m + ps;
values[1] = m + ps;
values[2] = m + ps;
values[3] = m + ps;
ffi_call(&cif, FFI_FN(fn), &rint, values);
CHECK((int)rint == 0);
#endif
exit(0);
}

View File

@ -0,0 +1,31 @@
/* Area: ffi_call
Purpose: Check zero-extension of pointers on x32.
Limitations: none.
PR: 887
Originator: Mikulas Patocka <mikulas@twibright.com> */
/* { dg-do run } */
#include "ffitest.h"
static int fn(int *a)
{
if (a)
return *a;
return -1;
}
int main(void)
{
ffi_cif cif;
ffi_type *args[MAX_ARGS];
void *values[MAX_ARGS];
void *z[2] = { (void *)0, (void *)1 };
ffi_arg rint;
args[0] = &ffi_type_pointer;
values[0] = z;
CHECK(ffi_prep_cif(&cif, ABI_NUM, 1, &ffi_type_sint, args) == FFI_OK);
ffi_call(&cif, FFI_FN(fn), &rint, values);
CHECK((int)rint == -1);
exit(0);
}