Jump to content
Sign in to follow this  
Tommi Prami

Getting RDSEED with Delphi

Recommended Posts

9 hours ago, DelphiUdIT said:

From what I know, the VirtualBox hypervisor for example runs as VMX ROOT and the virtual machine runs in ring 1 (OS) and ring 3 (user space). VirtualBox performs all checks and handling of all exceptions of both the guest operating system (OS in ring 1) and USER (in ring 3) ... including CPUIDs and all others if they occur.

I think we misunderstand each other here, i am sorry and will try to explain this point, sorry for my English again,

 

Due to many confusing terms, because they are really confusing and spread left and right, i will try with examples and questions (don't need an answers, just food for thought), because i can't keep up with these terms everywhere with big words.

first lets dissect the above text 

>VMX ROOT and the virtual machine, 

This is talking about the hypervisor it self, it moves further and talk ring 1 and ring 3, this make sense, but lets ask the real question how it did override the host kernel for ring 1, and what will happen with ring 0 ?!!

The guest lets say Windows server 2008 and the host is Windows 10, CPUID in 2008 must not be handled by Win10, then how the hardware generated fault override the host kernel for such very critical fault ?

another scenario, two guests one of them running critical web servers, the other will try to hack into it by calling RDSEED and RDRAND all the time to get a back log may be, this is critical security hole if any consequential 2 calls gave the same output, hence the failure ensured by the CPU, but and it is serious one, how to virtualize this thing assuming we need older compatibility to remove these instructions, does the host have to be triggered, and more serious question if these instruction executed by the guest, should the host ever know about them ? or they output values ? the answer is no.

If ring 0 which is such crucial to Windows kernel including our guest Win2008 did ring0, who is going to handle this, also there is one of the most critical instruction in the whole PC, the LGDT, the guest must be able to have its version while the host most not have any idea about this, any knowledge will render the whole hardware virtualization insecure and the guest will be compromised, and no one must trust any virtualized machine to run any server.

Also let differentiate between emulation and hardware virtualization, emulation will check each instruction and adjust it, making the performance slower, virtualization using modern CPU is almost transparent allowing all these instruction to run at base CPU speed, but when it comes to specific instructions, just like the host in protected mode, they will be handled in hardware designed manner, CPUID is not privileged https://www.felixcloutier.com/x86/cpuid and does have any exception with it (these 2 mentioned are irrelevant), yet another documentation (the one you provided) explicitly says it will raise fault-like VM exit, is that a contradiction ?, no 

Due the complexity of modern CPU's, they can't include these VM related faults everywhere talking about documentation, because they will happen in different environment, 

 

Now to the core question who must handle these fault in virtualized mode, not the guest, and not the host, it is one called hypervisor or VMX ROOT mode or non root mode , or whatever.... the point is, these will will be captured by specific kernel-like and protected mode like running on top of the host protected mode but to very extended length in dependably.

 

one point though, if the host get triggered to handle an instructions like INT the interrupt call https://www.felixcloutier.com/x86/intn:into:int3:int1 then literally the speed should be 10x-100x slower on the guest, we don't witness any thing like that, so the CPU after switching to a thread inside the hardware virtualized mode, it will use the same operation in that link but not for the same IDT of the host, this is way longer explanation, Intel documentation separate the virtualized mode completely from the default virtual and protected and real mode ....

10 hours ago, DelphiUdIT said:

So, no software should crash if CPUID is used ONLY when it is available, and this is true also for RDRAND and RDSEED of course:

sure, but like the above, this is true in all mode except the virtualized mode, CPUID on a host should not know the exact CPUID of the host !, and the host software should not know if a guest software called it !, only the what so called hypervisor will know and have the ability to change its return value.

 

Sorry for salad like phrases above, just hope they give you a start point to decipher these documentation, and put us on closer page.

Share this post


Link to post

Remembered or thought about more clear example.

 

What about page faults, by not only Intel documentation but every CPU, page fault should trigger specific hardware reaction, hence invoke the what so called kernel aka protected mode which in this case override the real mode, to handle this.

Now have you ever witness any correlation between the page faults triggered in guest software/OS and the host ? the answer is no .

How does this happen ?, by the virtualized mode where CPU change override its initially installed protected mode after its real mode and use the virtualized mode, this is not explained in the Intel CPU instructions but explained in different manuals and resources.

 

This why i feel my Delphi IDE somehow faster and smoother running on older Windows hosted by Windows 10 Hyper-V, the real cause is memory management the way page faults handled.

Share this post


Link to post
3 hours ago, Kas Ob. said:

I think we misunderstand each other here, i am sorry and will try to explain this point, sorry for my English again,

I also think we are misunderstanding each other.

To close, without going too OT, what you said is clear but we are confusing the issues.

The virtualized environment is one thing and is defined through VMX virtualization and other things are the various modes in which the various processes can run (Protect, Real, etc ...).

 

NORMAL PROCESS (NOT VIRTUALIZED)

In Intel hardware, the RING0 level is the level that has the highest priority and privilege for instructions in all modes. Very similar to RING1 and RING2, but which have major limitations on the use of privileged instructions. In fact, to the best of my knowledge, RING1 and RING2 are never used (with the exception of RING1 in VMX, or better in the real world implementation like in VirtualBox).

 

RING3 is used for user processes, because it has the lowest privilege level and therefore everything that happens can be monitored by RING0. For example, a fault in the instructions at the RING3 level is managed by the RING0 (normally Kernel) to implement the appropriate countermeasures (which can be either effective repair of the operation such as its emulation, or the management of a "page fault" or its actual rejection with generation of an error (and if this does not come from a user program but from a driver or the kernel then the infamous BSOD will most likely appear).

These functions have existed more or less since the first I386 was marketed. Then gradually perfected until we reach our times.

All exception management at the instruction level (which obviously also concerns the hardware) is normally managed at the kernel level which can decide to do what it deems most appropriate.

 

VIRTUALIZATION

The virtualization process goes beyond this... a new level of control called VMX ROOT is inserted which runs with privileges in a certain sense superior to those of a RING0 and which normally uses the VMX NOT ROOT environment to run another system operating. This is the difference. Then, to make things easier, normally RING1 (ALWAYS IN VMX NOT ROOT) is used to run the guest OS and RING3 (ALWAYS IN VMX NOT ROOT) for user applications.
While the VMX ROOT must coordinate with the normal HOST operating system, the VMX NOT ROOT has nothing to do with the host.

3 hours ago, Kas Ob. said:

The guest lets say Windows server 2008 and the host is Windows 10, CPUID in 2008 must not be handled by Win10, then how the hardware generated fault override the host kernel for such very critical fault ?

another scenario, two guests one of them running critical web servers, the other will try to hack into it by calling RDSEED and RDRAND all the time to get a back log may be, this is critical security hole if any consequential 2 calls gave the same output, hence the failure ensured by the CPU, but and it is serious one, how to virtualize this thing assuming we need older compatibility to remove these instructions, does the host have to be triggered, and more serious question if these instruction executed by the guest, should the host ever know about them ? or they output values ? the answer is no.

 

The CUPID instruction of your GUEST is not managed by the HOST operating system nor from the GUEST KERNEL, but rather by the VMX ROOT which will do what it has to do (it could easily execute it on behalf of the VMX NOT ROOT and report the result perhaps masked). Why don't we see a pronounced slowdown in guest operating systems? Because none of these perform certain operations continuously. Do you really think that a kernel, like a driver or an application, continuously executes a CPUID? Among other things, a very heavy instruction (for dozens of machine cycles if I'm not mistaken).

This process is very similar (but being in hardware it is obviously more efficient) to what happened before the use of VMX: VirtualBox works in a similar way even without VMX virtualization, again thanks to RING1 and RING3 but obviously with decidedly lower security compared to a VMX environment.

 

To be clear: VirtualBox hypervisor intercept every single CPUID instruction from VMX NOT ROOT to mask the result (the features that they don't support or they don't want exposed to guest) ... for example in the past they didn't support nexted VT-D virtualization and so the relative CPUID features is set to 0 and the virtual guest (kernel o user) saw THAT ...

 

This is announced support for RDSEED (9 years ago) always from VirtualBox:

 

image.thumb.png.e0321e4acf05366489df6a1741d3608d.png


All as I mentioned if we talk about virtualizing 32-bit environments. In 64-bit environments, additional issues are introduced and the techniques are more complex.

 

It's clear to me, but if there were to be any replies I would say to open another thread.

Edited by DelphiUdIT

Share this post


Link to post

@Tommi Prami

If you want commit the code in your repository and insert the test for the available of RDRAND and RDSEED, this is the code.
Project Jedi has more info about that and of course the Intel documentation has all the documentation.
I don't know about AMD, so i presume that AMD has the same two bits in use (hope).


Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit), not tested in real address mode.
 

interface

//Explicity check if RDRAND and RDSEEK ara avilable
//Global use

TCheck_RDRAND_RDSEED = record
   tc_RDRAND: boolean;  //true if RDRAND is available
   tc_RDSEED: boolean;  //true if RDSEED is available
end;

var RDRAND_RDSEED_Available: TCheck_RDRAND_RDSEED;

implementation

const
  //ID string to identify CPU Vendor, the are a multitude .. but we focalize on this
  VendorIDxIntel: array [0..11] of AnsiChar = 'GenuineIntel';
  VendorIDxAMD: array [0..11] of AnsiChar = 'AuthenticAMD';

//Internal functions, may be usefull to implement other check
//Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit), not tested in real address mode
//The Intel Documentation has more detail about CPUID
//Jedi project has implemented TCPUInfo with more details.

//First check that the CPU supports CPUID instructions. There are some exceptions with this rule,
//but with very very old processors
function Is_CPUID_Valid: boolean; register;
asm
 {$IFDEF WIN64}
  pushfq                               //Save EFLAGS
  pushfq                               //Store EFLAGS
  xor qword [esp], $00200000           //Invert the ID bit in stored EFLAGS
  popfq                                //Load stored EFLAGS (with ID bit inverted)
  pushfq                               //Store EFLAGS again (ID bit may or may not be inverted)
  pop rax                              //eax = modified EFLAGS (ID bit may or may not be inverted)
  xor rax, qword [esp]                 //eax = whichever bits were changed
  popfq                                //Restore original EFLAGS
  and RAX, $00200000                   //eax = zero if ID bit can't be changed, else non-zero
  jz @quit
  mov RAX, $01                         //If the Result is boolean, the return parameter should be in A??? (true if A??? <> 0)
  @quit:
 {$ELSE}
  pushfd                               //Save EFLAGS
  pushfd                               //Store EFLAGS
  xor dword [esp], $00200000           //Invert the ID bit in stored EFLAGS
  popfd                                //Load stored EFLAGS (with ID bit inverted)
  pushfd                               //Store EFLAGS again (ID bit may or may not be inverted)
  pop eax                              //eax = modified EFLAGS (ID bit may or may not be inverted)
  xor eax,[esp]                        //eax = whichever bits were changed
  popfd                                //Restore original EFLAGS
  and eax, $00200000                   //eax = zero if ID bit can't be changed, else non-zero
  jz @quit
  mov EAX, $01                         //If the Result is boolean, the return parameter should be in AL (true if AL <> 0)
  @quit:
 {$ENDIF}
end;

//1) Check that the CPU is an INTEL CPU, we don't know nothing about other's
//   We can presume the AMD modern processors have the same check of INTEL, but only for some instructions.
//   No test were made to verify this (no AMD processor available)
//
//2) Catch the features of the CPU in use
//
//3) Catch the new features of the CPU in use
//
procedure CPUID_GeneralCall(InEAX: cardinal; InECX: cardinal; out Reg_EAX, Reg_EBX, Reg_ECX, Reg_EDX); stdcall;
asm
 {$IFDEF WIN64}
  // save context
  PUSH RBX
  // CPUID
  MOV EAX, InEAX           //Generic function
  MOV ECX, InECX           //Generic sub function
  //
  //For CPU VENDOR STRING EAX := $0
  //ECX is not used when EAX = $0
  //
  //For CPU Extension EAX := $01
  //ECX is not used when EAX = $01
  //
  //For CPU New Extension EAX := $07
  //ECX should be $00 to read if RDSEED is available
  //
  CPUID
  // store results
  MOV R8, Reg_EAX
  MOV R9, Reg_EBX
  MOV R10, Reg_ECX
  MOV R11, Reg_EDX
  MOV Cardinal PTR [R8], EAX
  MOV Cardinal PTR [R9], EBX
  MOV Cardinal PTR [R10], ECX
  MOV Cardinal PTR [R11], EDX
  // restore context
  POP RBX
 {$ELSE}
  // save context
  PUSH    EDI
  PUSH    EBX
  // CPUID
  MOV EAX, InEAX           //Generic function
  MOV ECX, InECX           //Generic sub function
  //
  //For CPU VENDOR STRING EAX := $0
  //ECX is not used when EAX = $0
  //
  //For CPU Extension EAX := $01
  //ECX is not used when EAX = $01
  //
  //For CPU New Extension EAX := $07
  //ECX should be $00 to read if RDSEED is available
  //
  CPUID
  // store results
  MOV EDI, Reg_EAX
  MOV Cardinal PTR [EDI], EAX
  MOV EAX, Reg_EBX
  MOV EDI, Reg_ECX
  MOV Cardinal PTR [EAX], EBX
  MOV Cardinal PTR [EDI], ECX
  MOV EAX, Reg_EDX
  MOV Cardinal PTR [EAX], EDX
  // restore context
  POP EBX
  POP EDI
 {$ENDIF}
end;

//Function called from Initialization
function CPUID_RDRAND_RDSEEK_Check: TCheck_RDRAND_RDSEED;
var
  tempVendorId: array [0..11] of AnsiChar;
  HighValBase: Cardinal;
  HighValExt1: Cardinal;
  VersionInfo: Cardinal;
  AdditionalInfo: Cardinal;
  ExFeatures: Cardinal;
  StdFeatures: Cardinal;
  UnUsed1, UnUsed2: Cardinal;
  NewFeatures: Cardinal;
begin
  Result.tc_RDRAND := false;
  Result.tc_RDSEED := false;
  //Check if CPUID istruction is valid testing the bit 21 of EFLAGS
  if Is_CPUID_Valid then
    begin
      //Get the Vendor string with EAX = 0 and ECX = 0
      CPUID_GeneralCall(0, 0, HighValBase, tempVendorID[0], tempVendorID[8], tempVendorID[4]);
      //Verifiy that we are on CPU that we support
      if (tempVendorId = VendorIDxIntel) OR (tempVendorId = VendorIDxAMD) then
        begin
          //Now check if RDRAND and RDSEED is supported inside the extended CPUID flags
          if HighValbase >=1 then  //Supports extensions
            begin
              //With EAX = 1 AND ECX = 0 the Extension and the available of RDRAND can be read
              CPUID_GeneralCall(1, 0, VersionInfo, AdditionalInfo, ExFeatures, StdFeatures);
              //ExFeatures (ECX register) bit 30 is 1 if RDRAND is available
              if (ExFeatures and ($1 shl 30)) <> 0 then
                Result.tc_RDRAND := true;
              if HighValBase >= 7 then
                begin
                  //With EAX = 7 AND ECX = 0 the NEW Extension and the available of RDSEED can be read
                  CPUID_GeneralCall(7, 0, HighValExt1, NewFeatures, UnUsed1, UnUsed2);
                  //New Features (EBX register) bit 18 is 1 if RDSEED is available
                  if (NewFeatures and ($1 shl 18)) <> 0 then
                    Result.tc_RDSEED := true;
                end;
            end;
        end;
    end;
end;

Initialization
  begin
    RDRAND_RDSEED_Available := CPUID_RDRAND_RDSEEK_Check;
  end;
end.

Bye

Edited by DelphiUdIT

Share this post


Link to post
On 10/9/2023 at 7:43 PM, DelphiUdIT said:

@Tommi Prami

If you want commit the code in your repository and insert the test for the available of RDRAND and RDSEED, this is the code.
Project Jedi has more info about that and of course the Intel documentation has all the documentation.
I don't know about AMD, so i presume that AMD has the same two bits in use (hope).


Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit), not tested in real address mode.
 


interface

//Explicity check if RDRAND and RDSEEK ara avilable
//Global use

TCheck_RDRAND_RDSEED = record
   tc_RDRAND: boolean;  //true if RDRAND is available
   tc_RDSEED: boolean;  //true if RDSEED is available
end;

var RDRAND_RDSEED_Available: TCheck_RDRAND_RDSEED;

implementation

const
  //ID string to identify CPU Vendor, the are a multitude .. but we focalize on this
  VendorIDxIntel: array [0..11] of AnsiChar = 'GenuineIntel';
  VendorIDxAMD: array [0..11] of AnsiChar = 'AuthenticAMD';

//Internal functions, may be usefull to implement other check
//Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit), not tested in real address mode
//The Intel Documentation has more detail about CPUID
//Jedi project has implemented TCPUInfo with more details.

//First check that the CPU supports CPUID instructions. There are some exceptions with this rule,
//but with very very old processors
function Is_CPUID_Valid: boolean; register;
asm
 {$IFDEF WIN64}
  pushfq                               //Save EFLAGS
  pushfq                               //Store EFLAGS
  xor qword [esp], $00200000           //Invert the ID bit in stored EFLAGS
  popfq                                //Load stored EFLAGS (with ID bit inverted)
  pushfq                               //Store EFLAGS again (ID bit may or may not be inverted)
  pop rax                              //eax = modified EFLAGS (ID bit may or may not be inverted)
  xor rax, qword [esp]                 //eax = whichever bits were changed
  popfq                                //Restore original EFLAGS
  and RAX, $00200000                   //eax = zero if ID bit can't be changed, else non-zero
  jz @quit
  mov RAX, $01                         //If the Result is boolean, the return parameter should be in A??? (true if A??? <> 0)
  @quit:
 {$ELSE}
  pushfd                               //Save EFLAGS
  pushfd                               //Store EFLAGS
  xor dword [esp], $00200000           //Invert the ID bit in stored EFLAGS
  popfd                                //Load stored EFLAGS (with ID bit inverted)
  pushfd                               //Store EFLAGS again (ID bit may or may not be inverted)
  pop eax                              //eax = modified EFLAGS (ID bit may or may not be inverted)
  xor eax,[esp]                        //eax = whichever bits were changed
  popfd                                //Restore original EFLAGS
  and eax, $00200000                   //eax = zero if ID bit can't be changed, else non-zero
  jz @quit
  mov EAX, $01                         //If the Result is boolean, the return parameter should be in AL (true if AL <> 0)
  @quit:
 {$ENDIF}
end;

//1) Check that the CPU is an INTEL CPU, we don't know nothing about other's
//   We can presume the AMD modern processors have the same check of INTEL, but only for some instructions.
//   No test were made to verify this (no AMD processor available)
//
//2) Catch the features of the CPU in use
//
//3) Catch the new features of the CPU in use
//
procedure CPUID_GeneralCall(InEAX: cardinal; InECX: cardinal; out Reg_EAX, Reg_EBX, Reg_ECX, Reg_EDX); stdcall;
asm
 {$IFDEF WIN64}
  // save context
  PUSH RBX
  // CPUID
  MOV EAX, InEAX           //Generic function
  MOV ECX, InECX           //Generic sub function
  //
  //For CPU VENDOR STRING EAX := $0
  //ECX is not used when EAX = $0
  //
  //For CPU Extension EAX := $01
  //ECX is not used when EAX = $01
  //
  //For CPU New Extension EAX := $07
  //ECX should be $00 to read if RDSEED is available
  //
  CPUID
  // store results
  MOV R8, Reg_EAX
  MOV R9, Reg_EBX
  MOV R10, Reg_ECX
  MOV R11, Reg_EDX
  MOV Cardinal PTR [R8], EAX
  MOV Cardinal PTR [R9], EBX
  MOV Cardinal PTR [R10], ECX
  MOV Cardinal PTR [R11], EDX
  // restore context
  POP RBX
 {$ELSE}
  // save context
  PUSH    EDI
  PUSH    EBX
  // CPUID
  MOV EAX, InEAX           //Generic function
  MOV ECX, InECX           //Generic sub function
  //
  //For CPU VENDOR STRING EAX := $0
  //ECX is not used when EAX = $0
  //
  //For CPU Extension EAX := $01
  //ECX is not used when EAX = $01
  //
  //For CPU New Extension EAX := $07
  //ECX should be $00 to read if RDSEED is available
  //
  CPUID
  // store results
  MOV EDI, Reg_EAX
  MOV Cardinal PTR [EDI], EAX
  MOV EAX, Reg_EBX
  MOV EDI, Reg_ECX
  MOV Cardinal PTR [EAX], EBX
  MOV Cardinal PTR [EDI], ECX
  MOV EAX, Reg_EDX
  MOV Cardinal PTR [EAX], EDX
  // restore context
  POP EBX
  POP EDI
 {$ENDIF}
end;

//Function called from Initialization
function CPUID_RDRAND_RDSEEK_Check: TCheck_RDRAND_RDSEED;
var
  tempVendorId: array [0..11] of AnsiChar;
  HighValBase: Cardinal;
  HighValExt1: Cardinal;
  VersionInfo: Cardinal;
  AdditionalInfo: Cardinal;
  ExFeatures: Cardinal;
  StdFeatures: Cardinal;
  UnUsed1, UnUsed2: Cardinal;
  NewFeatures: Cardinal;
begin
  Result.tc_RDRAND := false;
  Result.tc_RDSEED := false;
  //Check if CPUID istruction is valid testing the bit 21 of EFLAGS
  if Is_CPUID_Valid then
    begin
      //Get the Vendor string with EAX = 0 and ECX = 0
      CPUID_GeneralCall(0, 0, HighValBase, tempVendorID[0], tempVendorID[8], tempVendorID[4]);
      //Verifiy that we are on CPU that we support
      if (tempVendorId = VendorIDxIntel) OR (tempVendorId = VendorIDxAMD) then
        begin
          //Now check if RDRAND and RDSEED is supported inside the extended CPUID flags
          if HighValbase >=1 then  //Supports extensions
            begin
              //With EAX = 1 AND ECX = 0 the Extension and the available of RDRAND can be read
              CPUID_GeneralCall(1, 0, VersionInfo, AdditionalInfo, ExFeatures, StdFeatures);
              //ExFeatures (ECX register) bit 30 is 1 if RDRAND is available
              if (ExFeatures and ($1 shl 30)) <> 0 then
                Result.tc_RDRAND := true;
              if HighValBase >= 7 then
                begin
                  //With EAX = 7 AND ECX = 0 the NEW Extension and the available of RDSEED can be read
                  CPUID_GeneralCall(7, 0, HighValExt1, NewFeatures, UnUsed1, UnUsed2);
                  //New Features (EBX register) bit 18 is 1 if RDSEED is available
                  if (NewFeatures and ($1 shl 18)) <> 0 then
                    Result.tc_RDSEED := true;
                end;
            end;
        end;
    end;
end;

Initialization
  begin
    RDRAND_RDSEED_Available := CPUID_RDRAND_RDSEEK_Check;
  end;
end.

Bye

Please make Pull request.

 

-Tee-

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×