Page MenuHomeFreeBSD

amd64 ia32_syscall(): only allow for ILP32 processes
ClosedPublic

Authored by kib on Sat, Apr 25, 9:59 AM.
Tags
None
Referenced Files
F154314494: D56630.diff
Mon, Apr 27, 7:02 PM
F154260598: D56630.diff
Mon, Apr 27, 11:20 AM
Unknown Object (File)
Sat, Apr 25, 4:09 PM
Subscribers

Details

Summary
64bit processes can issue INT $0x80 instruction, and get the syscall
dispatched through ia32_syscall().  This works because syscall argument
fetch and result return are selected from the process sysent.

But, ia32_syscall() does not verify some conditions and does not perform
some actions which are considered unnecessary because the caller is
supposed to only access lower 4G.  The INT syscall path breaks this
assumption.

We never supported such hack, so disable it.  Send the offending thread
SIGBUS as if #GP was issued by hardware due to IDT vector 0x80 having
not enough DPL value.


amd64: ia32_fetch_syscall_args() does not need to check params != NULL

Whatever params pointer is, it does not matter.  copyin() handles any
values.  In fact, params cannot be ever NULL.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

kib requested review of this revision.Sat, Apr 25, 9:59 AM

This is the test program I used

	.text
	.globl	_start
_start:	movl	$4, %eax
	movq	$0, %rdi
	movq	$string, %rsi
	movq	$string_end-string, %rdx
	int	$0x80
	movl	$1, %eax
	movq	$0, %rdi
	int	$0x80
	jmp	.
string:	.ascii	"hello\n"
string_end:

But, ia32_syscall() does not verify some conditions and does not perform some actions which are considered unnecessary because the caller is supposed to only access lower 4G.

What conditions and actions does this refer to?

But, ia32_syscall() does not verify some conditions and does not perform some actions which are considered unnecessary because the caller is supposed to only access lower 4G.

What conditions and actions does this refer to?

For instance

	if (__predict_false(td->td_frame->tf_rip >= (la57 ?
	    VM_MAXUSER_ADDRESS_LA57 : VM_MAXUSER_ADDRESS_LA48)))
		set_pcb_flags(td->td_pcb, PCB_FULL_IRET);

	amd64_syscall_ret_flush_l1d_check_inline(td->td_errno);

There is more to it, for instance fsbase/gsbase are not properly saved in the int80 entry point.

In D56630#1297274, @kib wrote:

But, ia32_syscall() does not verify some conditions and does not perform some actions which are considered unnecessary because the caller is supposed to only access lower 4G.

What conditions and actions does this refer to?

For instance

	if (__predict_false(td->td_frame->tf_rip >= (la57 ?
	    VM_MAXUSER_ADDRESS_LA57 : VM_MAXUSER_ADDRESS_LA48)))
		set_pcb_flags(td->td_pcb, PCB_FULL_IRET);

This was added to address some vulnerability specific to syscall/sysret? I am not sure.

	amd64_syscall_ret_flush_l1d_check_inline(td->td_errno);

This is present in ia32_syscall().

There is more to it, for instance fsbase/gsbase are not properly saved in the int80 entry point.

So, isn't the SV_ILP32 check insufficient? A program can brand itself as a 32-bit executable and still manipulate the user gsbase via sysarch(I386_SET_GSBASE).

sys/amd64/ia32/ia32_syscall.c
220

I think these assignments can come after the check?

In D56630#1297274, @kib wrote:

But, ia32_syscall() does not verify some conditions and does not perform some actions which are considered unnecessary because the caller is supposed to only access lower 4G.

What conditions and actions does this refer to?

For instance

	if (__predict_false(td->td_frame->tf_rip >= (la57 ?
	    VM_MAXUSER_ADDRESS_LA57 : VM_MAXUSER_ADDRESS_LA48)))
		set_pcb_flags(td->td_pcb, PCB_FULL_IRET);

This was added to address some vulnerability specific to syscall/sysret? I am not sure.

There is a comment above this fragment in the trap.c which explains it. It was FreeBSD-SA-12:04.sysret

	amd64_syscall_ret_flush_l1d_check_inline(td->td_errno);

This is present in ia32_syscall().

There is more to it, for instance fsbase/gsbase are not properly saved in the int80 entry point.

So, isn't the SV_ILP32 check insufficient? A program can brand itself as a 32-bit executable and still manipulate the user gsbase via sysarch(I386_SET_GSBASE).

It is fine to use the syscall. Problems are caused by WRFSBASE/WRGSBASE instructions, which are only available in 64bit mode.

In D56630#1296884, @kib wrote:

This is the test program I used

	.text
	.globl	_start
_start:	movl	$4, %eax
	movq	$0, %rdi
	movq	$string, %rsi
	movq	$string_end-string, %rdx
	int	$0x80
	movl	$1, %eax
	movq	$0, %rdi
	int	$0x80
	jmp	.
string:	.ascii	"hello\n"
string_end:

Will you convert it to a test program that we can add to the regression test suite? It's fine to add plain C programs there, i.e., no ATF.

This revision is now accepted and ready to land.Sun, Apr 26, 2:00 PM
In D56630#1296884, @kib wrote:

This is the test program I used

	.text
	.globl	_start
_start:	movl	$4, %eax
	movq	$0, %rdi
	movq	$string, %rsi
	movq	$string_end-string, %rdx
	int	$0x80
	movl	$1, %eax
	movq	$0, %rdi
	int	$0x80
	jmp	.
string:	.ascii	"hello\n"
string_end:

Will you convert it to a test program that we can add to the regression test suite? It's fine to add plain C programs there, i.e., no ATF.

This snippet cannot become a test program, since it works before the patch, but is killed by SIGBUS on the first int 80 after.
The proper test program would need to catch SIGBUS, try int 80, and confirm that it got SIGBUS instead of some syscall action.
I can write it, but it is definitely not this asm program.

sys/amd64/ia32/ia32_syscall.c
220

Only orig_tf_rflags can be moved, I think.
But due to __predict_false() and general low probability of that path, I do not think that the rearrangement is worth it.

In D56630#1297513, @kib wrote:
In D56630#1296884, @kib wrote:

This is the test program I used

	.text
	.globl	_start
_start:	movl	$4, %eax
	movq	$0, %rdi
	movq	$string, %rsi
	movq	$string_end-string, %rdx
	int	$0x80
	movl	$1, %eax
	movq	$0, %rdi
	int	$0x80
	jmp	.
string:	.ascii	"hello\n"
string_end:

Will you convert it to a test program that we can add to the regression test suite? It's fine to add plain C programs there, i.e., no ATF.

This snippet cannot become a test program, since it works before the patch, but is killed by SIGBUS on the first int 80 after.
The proper test program would need to catch SIGBUS, try int 80, and confirm that it got SIGBUS instead of some syscall action.
I can write it, but it is definitely not this asm program.

Or fork a child, invoke the snippet there, and check for SIGBUS in the parent.

It would be nice to have it IMO.