Re: [PATCH] x86: vdso32/syscall.S: do not load __USER32_DS to %ss

From: Denys Vlasenko
Date: Tue Mar 24 2015 - 16:17:44 EST


On 03/24/2015 05:55 PM, Brian Gerst wrote:
>>> Might be nice to place a more generic description there, which
>>> registers are expected to be saved by user-space calling in here, etc.
>>
>> __kernel_vsyscall entry point has the same ABI in any 32-bit vDSO,
>> the good old int 0x80 calling convention:
>>
>> syscall# in eax,
>> params in ebx/ecx/edx/esi/edi/ebp,
>> all registers are preserved by the syscall.
>>
>> (I think we don't guarantee that all flags are preserved:
>> I have a testcase where DF gets cleared).
>
> DF should always be clear on any function call per the C ABI. But,
> eflags should be preserved, at least the non-privileged bits. I'd
> like to see that testcase.

The testcase is a simplistic example of how to find and use
32-bit vDSO to perform system calls.

It also sets flags.DF before syscall, and checks whether registers
are preserved, including flags.DF.

On 32-bit kernel (on Intel CPU, where vDSO uses SYSENTER), I see this:

$ ./test32_syscall_vdso
Result:1

whereas on 64-bit it is

./test32_syscall_vdso
Result:0

"Result:1" means that DF was cleared.

See attached source.

// (Assuming that int 80 works,) test that syscall32/sysenter32 works
//
// i686-gcc -Os -Wall -static
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <elf.h>

long syscall_addr;
long get_syscall(char **envp)
{
Elf32_auxv_t *auxv;
while (*envp++ != NULL)
continue;
for (auxv = (void *)envp; auxv->a_type != AT_NULL; auxv++)
if( auxv->a_type == AT_SYSINFO)
return auxv->a_un.a_val;
fprintf(stderr, "AT_SYSINFO not supplied, can't test\n");
exit(0); /* this is not a failure */
}

int nfds;
fd_set rfds;
fd_set wfds;
fd_set efds;
struct timespec timeout;
sigset_t sigmask;
struct {
sigset_t *sp;
int sz;
} sigmask_desc;

char RES[] = "Result:0\n";

void prep_args()
{
nfds = 42;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
FD_SET(0, &rfds);
FD_SET(1, &wfds);
FD_SET(2, &efds);
timeout.tv_sec = 0;
timeout.tv_nsec = 123;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
sigaddset(&sigmask, SIGUSR2);
sigaddset(&sigmask, SIGRTMAX);
sigmask_desc.sp = &sigmask;
sigmask_desc.sz = 8; /* bytes */
}

#define PSELECT 308
// pselect(nfds,rfds,wfds,efds,timeout,sigmask)
int main(int argc, char **argv, char **envp)
{
// This only works for non-static builds:
//syscall_addr = dlsym(dlopen("linux-gate.so.1", RTLD_NOW), "__kernel_vsyscall");
syscall_addr = get_syscall(envp);
prep_args();

asm("\n"
// Try 6-arg syscall: pselect. It should return quickly
" mov $308,%eax\n" // PSELECT
" mov nfds,%ebx\n" // ebx arg1
" mov $rfds,%ecx\n" // ecx arg2
" mov $wfds,%edx\n" // edx arg3
" mov $efds,%esi\n" // esi arg4
" mov $timeout,%edi\n" // edi arg5
" mov $sigmask_desc,%ebp\n" // %ebp arg6
" std\n"
" call *syscall_addr\n"
// Check that registers are not clobbered
" pushf\n"
" pop %eax\n"
" cmp nfds,%ebx\n" // ebx arg1
" movb $0x32,RES+7\n"
" jne end\n"
" cmp $rfds,%ecx\n" // ecx arg2
" movb $0x33,RES+7\n"
" jne end\n"
" cmp $wfds,%edx\n" // edx arg3
" movb $0x34,RES+7\n"
" jne end\n"
" cmp $efds,%esi\n" // esi arg4
" movb $0x35,RES+7\n"
" jne end\n"
" cmp $timeout,%edi\n" // edi arg5
" movb $0x36,RES+7\n"
" jne end\n"
" cmpl $sigmask_desc,%ebp\n" // %ebp arg6
" movb $0x37,RES+7\n"
" jne end\n"
// This fails (DF is not preserved)
// on 32-bit kernels. 64-bit ones worked:
" bt $10,%eax\n" //flags.DF still set?
" movb $0x31,RES+7\n"
" jnc end\n"

" movb $0x30,RES+7\n"

"end:\n"
" mov $4,%eax\n" // WRITE
" mov $1,%ebx\n" // ebx arg1
" mov $RES,%ecx\n" // ecx arg2
" mov $9,%edx\n" // edx arg3
" int $0x80\n"

// Error if result is not 0 or 1
// (result:1 means DF was clobbered)
" mov $1,%eax\n" // EXIT
" xor %ebx,%ebx\n" // ebx arg1
" cmpb $0x31,RES+7\n"
" ja 1f\n"
" int $0x80\n"
"1: inc %ebx\n"
" int $0x80\n"
);
return 42; /* not reached */
}