New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] The ultimate ros amd64 bringup #361
Changes from 1 commit
91c8699
ff1eaad
15a08e8
cce3d16
286b2fb
12002d4
546c05b
1bc15af
1f6d61b
9e097de
aedb9f6
d139743
25064d0
75e11df
13ef07a
03920b1
235566d
1e326fb
c4fbc15
29db059
c24203c
c9d1f6a
e503f53
9335f8f
4b4a334
945d807
a26ae1d
bd078ab
7749bdc
177b3e7
52721c2
fbec870
d5bdd7e
4351118
5b1588c
1dc22b6
8366b2c
29d20df
10e086b
3252ac3
66eb02c
c93d924
c3380ef
c249c15
8b48097
e7e7e40
a9fc91d
6e0a3be
f062e2d
21177bb
ebe9bc4
c8bd63f
4c80d83
96f3020
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
/* INCLUDES ******************************************************************/ | ||
|
||
#include <ntoskrnl.h> | ||
#include <fltkernel.h> | ||
|
||
#define NDEBUG | ||
#include <debug.h> | ||
|
@@ -17,6 +18,10 @@ KiRetireDpcListInDpcStack( | |
PKPRCB Prcb, | ||
PVOID DpcStack); | ||
|
||
NTSTATUS | ||
KiConvertToGuiThread( | ||
VOID); | ||
|
||
VOID | ||
NTAPI | ||
KiDpcInterruptHandler(VOID) | ||
|
@@ -86,13 +91,66 @@ KeZeroPages(IN PVOID Address, | |
RtlZeroMemory(Address, Size); | ||
} | ||
|
||
PVOID | ||
KiSwitchKernelStackHelper( | ||
LONG_PTR StackOffset, | ||
PVOID OldStackBase); | ||
|
||
PVOID | ||
NTAPI | ||
KeSwitchKernelStack(PVOID StackBase, PVOID StackLimit) | ||
{ | ||
UNIMPLEMENTED; | ||
__debugbreak(); | ||
return NULL; | ||
PKTHREAD CurrentThread; | ||
PVOID OldStackBase; | ||
LONG_PTR StackOffset; | ||
SIZE_T StackSize; | ||
|
||
/* Get the current thread */ | ||
CurrentThread = KeGetCurrentThread(); | ||
|
||
/* Save the old stack base */ | ||
OldStackBase = CurrentThread->StackBase; | ||
|
||
/* Get the size of the current stack */ | ||
StackSize = (ULONG_PTR)CurrentThread->StackBase - CurrentThread->StackLimit; | ||
ASSERT(StackSize <= (ULONG_PTR)StackBase - (ULONG_PTR)StackLimit); | ||
|
||
/* Copy the current stack contents to the new stack */ | ||
RtlCopyMemory((PUCHAR)StackBase - StackSize, | ||
(PVOID)CurrentThread->StackLimit, | ||
StackSize); | ||
|
||
/* Calculate the offset between the old and the new stack */ | ||
StackOffset = (PUCHAR)StackBase - (PUCHAR)CurrentThread->StackBase; | ||
|
||
/* Disable interrupts while messing with the stack */ | ||
_disable(); | ||
|
||
/* Set the new trap frame */ | ||
CurrentThread->TrapFrame = (PKTRAP_FRAME)Add2Ptr(CurrentThread->TrapFrame, | ||
StackOffset); | ||
|
||
/* Set the new initial stack */ | ||
CurrentThread->InitialStack = Add2Ptr(CurrentThread->InitialStack, | ||
StackOffset); | ||
|
||
/* Set the new stack limits */ | ||
CurrentThread->StackBase = StackBase; | ||
CurrentThread->StackLimit = (ULONG_PTR)StackLimit; | ||
CurrentThread->LargeStack = TRUE; | ||
|
||
/* Adjust the PCR fields */ | ||
__addgsqword(FIELD_OFFSET(KPCR, NtTib.StackBase), StackOffset); | ||
__addgsqword(FIELD_OFFSET(KIPCR, Prcb.RspBase), StackOffset); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fail to see how, except for those two lines, this function is specific to amd64. Can't we factor out a general x86 version ? |
||
|
||
/* Finally switch RSP to the new stack. | ||
We pass OldStackBase to make sure it is not lost. */ | ||
OldStackBase = KiSwitchKernelStackHelper(StackOffset, OldStackBase); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Certain compilers with certain options may use rbp as a frame pointer, which your code wouldn't translate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and if we are honest, we can't do it this way on x86 either, unless all the code that leads to this is entirely written in assembly. For x64 I have a solution that is "by the book", i.e. it returns to asm code, then switches the stack and repeats the system call handling. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the code so that it should fix the issue. This is x64 only (because there the logic of returning to the "root" is already there, while on x86 we still rely on "good faith" that the compiler doesn't do totally awkward stuff. |
||
|
||
/* Reenable interrupts */ | ||
_enable(); | ||
|
||
return OldStackBase; | ||
} | ||
|
||
NTSTATUS | ||
|
@@ -315,9 +373,7 @@ KiSystemCallHandler( | |
PULONG64 KernelParams, UserParams; | ||
ULONG ServiceNumber, Offset, Count; | ||
ULONG64 UserRsp; | ||
|
||
DPRINT("Syscall #%ld\n", TrapFrame->Rax); | ||
//__debugbreak(); | ||
NTSTATUS Status; | ||
|
||
/* Increase system call count */ | ||
__addgsdword(FIELD_OFFSET(KIPCR, Prcb.KeSystemCalls), 1); | ||
|
@@ -354,6 +410,40 @@ KiSystemCallHandler( | |
/* Get descriptor table */ | ||
DescriptorTable = (PVOID)((ULONG_PTR)Thread->ServiceTable + Offset); | ||
|
||
/* Validate the system call number */ | ||
if (ServiceNumber >= DescriptorTable->Limit) | ||
{ | ||
/* Check if this is a GUI call */ | ||
if (!(Offset & SERVICE_TABLE_TEST)) | ||
{ | ||
/* Fail the call */ | ||
TrapFrame->Rax = STATUS_INVALID_SYSTEM_SERVICE; | ||
return (PVOID)NtSyscallFailure; | ||
} | ||
|
||
/* Convert us to a GUI thread -- must wrap in ASM to get new EBP */ | ||
Status = KiConvertToGuiThread(); | ||
|
||
/* Reload trap frame and descriptor table pointer from new stack */ | ||
TrapFrame = *(volatile PVOID*)&Thread->TrapFrame; | ||
DescriptorTable = (PVOID)(*(volatile ULONG_PTR*)&Thread->ServiceTable + Offset); | ||
|
||
if (!NT_SUCCESS(Status)) | ||
{ | ||
/* Set the last error and fail */ | ||
TrapFrame->Rax = Status; | ||
return (PVOID)NtSyscallFailure; | ||
} | ||
|
||
/* Validate the system call number again */ | ||
if (ServiceNumber >= DescriptorTable->Limit) | ||
{ | ||
/* Fail the call */ | ||
TrapFrame->Rax = STATUS_INVALID_SYSTEM_SERVICE; | ||
return (PVOID)NtSyscallFailure; | ||
} | ||
} | ||
|
||
/* Get stack bytes and calculate argument count */ | ||
Count = DescriptorTable->Number[ServiceNumber] / 8; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ EXTERN KiGeneralProtectionFaultHandler:PROC | |
EXTERN KiXmmExceptionHandler:PROC | ||
EXTERN KiDeliverApc:PROC | ||
EXTERN KiDpcInterruptHandler:PROC | ||
EXTERN PsConvertToGuiThread:PROC | ||
|
||
#ifdef _WINKD_ | ||
EXTERN KdSetOwedBreakpoints:PROC | ||
|
@@ -966,6 +967,28 @@ FUNC KiZwSystemService | |
|
||
ENDFUNC | ||
|
||
PUBLIC KiConvertToGuiThread | ||
FUNC KiConvertToGuiThread | ||
|
||
push rbp | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, here's something I just learned... x64 functions must not begin with a single-byte instruction (/hotpatch is always implicitly on, and no NOP is used). PSDK's shared/macamd64.inc has a rex_push_reg macro that explains this (somewhat). MS uses a redundant 0x48 REX prefix to ensure this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably create that include file and start using it. Seems like the right way to make things portable (eventually) and avoid mistakes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
And what's the technical/philosophical reason behind this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I answered that in parentheses right after the part you quoted. Hotpatching needs a two byte instruction (otherwise a running instance of the function might be in the middle of the first, then try to read the second instruction which has been overwritten with the second half of the patched-in jmp). x64 doesn't use "mov edi, edi", it mandates instead that functions must start with a two-byte (or larger) instruction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh year, hotpatching KiConvertToGuiThread is totally important! What could happen, if we didn't support that? Guess what: On Windows it starts with a 1 byte intruction, too! And KiInterruptTemplate also! I say: let all internal functions start with 1 byte, to make patching unreliable for nasty hackers, since that are the only people who would do that. |
||
.pushreg rbp | ||
sub rsp, 32 | ||
.allocstack 32 | ||
.endprolog | ||
|
||
/* Call the worker function */ | ||
call PsConvertToGuiThread | ||
|
||
/* Adjust rsp */ | ||
add rsp, 32 | ||
|
||
/* Restore rbp */ | ||
pop rbp | ||
|
||
/* return to the caller */ | ||
ret | ||
|
||
ENDFUNC | ||
|
||
KiExitToUserApc: | ||
int 3 | ||
|
@@ -1008,6 +1031,28 @@ KiInitializeSegments: | |
mov gs, ax | ||
ret | ||
|
||
/*! | ||
* VOID | ||
* KiSwitchKernelStackHelper( | ||
* LONG_PTR StackOffset, | ||
* PVOID OldStackBase); | ||
*/ | ||
PUBLIC KiSwitchKernelStackHelper | ||
KiSwitchKernelStackHelper: | ||
|
||
/* Pop return address from the current stack */ | ||
pop rax | ||
|
||
/* Switch to new stack */ | ||
lea rsp, [rsp + rcx] | ||
|
||
/* Push return address on the new stack */ | ||
push rax | ||
|
||
/* Return on new stack */ | ||
mov rax, rdx | ||
ret; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Superfluous ; |
||
|
||
|
||
#ifdef _MSC_VER | ||
#undef lgdt | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a note that this can be optimized by only copying what's above (rsp-32) or some such?