reasons MmMapLockedPagesSpecifyCache throwing exception - c++

I am a total newbie windows drivers so there may be obvious mistakes in the code.
My code allocates user space dma buffer which work fine accept when a lot of
buffers are allocated (not necessarily large buffers) on that case
MmMapLockedPagesSpecifyCache throws an exception.
The code stages that fails is as followed:
1. Use MmMapIoSpace to map physical address to kernel mode virtual address,
driver can access this virtual address, but its not accessible in user mode.
2. Use IoAllocateMdl and MmBuildMdlForNonPagedPool to build an MDL for the
mapped physical address.
3. Use MmMapLockedPagesSpecifyCache to map the physical pages described by MDL
to user mode virtual address.
Since our driver will always be the topmost driver, and run in the context of
the current process, this user mode virtual address is valid to the caller.
code snippet:
phy_buf->kernel_addr = MmMapIoSpace(phy_buf->phy_addr, phy_buf->size, MmNonCached);
phy_buf->user_mdl = IoAllocateMdl(
phy_buf->kernel_addr,
phy_buf->size,
FALSE,
FALSE,
NULL);
if(phy_buf->user_mdl)
{
MmBuildMdlForNonPagedPool(phy_buf->user_mdl);
__try{
phy_buf->user_addr = MmMapLockedPagesSpecifyCache(
phy_buf->user_mdl,
UserMode,
MmNonCached,
NULL,
FALSE,
NormalPagePriority) ;
}__except(EXCEPTION_EXECUTE_HANDLER) {
IoFreeMdl(phy_buf->user_mdl);
phy_buf->user_mdl = NULL;
DbgMessage(FATAL, "MmMapLockedPagesSpecifyCache failed on EXCEPTION_EXECUTE_HANDLER\n");
return NULL;
}
if (phy_buf->user_addr == NULL)
{
IoFreeMdl(phy_buf->user_mdl);
phy_buf->user_mdl = NULL;
DbgMessage(FATAL, "MmMapLockedPagesSpecifyCache failed\n");
}
Also how can I get (or print) exact exception that is thrown.

Related

Cannot set Scanner Capability because L_TwainStartCapsNeg returns error -84

I'm trying to use the Leadtools API version 21 for automatically scanning some documents and here is a sudo code of what I have done (it runs in a secondary thread and the unlock has been done in the main thread):
void CheckRetCode(int rc)
{
if (SUCCESS != rc)
{
L_TCHAR errMsg[1024];
memset(errMsg, 0, sizeof(errMsg));
L_GetFriendlyErrorMessage(rc, errMsg, 1024, L_FALSE);
throw TLeadException(errMsg, rc);
}
}
void OnThreadExecute(void)
{
HTWAINSESSION hSession = nullptr;
APPLICATIONDATA appData;
L_INT nRet;
L_TCHAR pszTwnSourceName[1024];
LTWAINSOURCE sInfo;
memset(&appData, 0, sizeof(APPLICATIONDATA));
appData.uStructSize = sizeof(APPLICATIONDATA);
appData.hWnd = hWnd;// hWnd is valid handle of my main window
appData.uLanguage = TWLG_ENGLISH_USA;
appData.uCountry = TWCY_USA;
wcscpy(appData.szManufacturerName, L"MyCompanyName");
wcscpy(appData.szAppProductFamily, L"MyProductName");
wcscpy(appData.szAppName, appData.szAppProductFamily);
wcscpy(appData.szVersionInfo, L"Version 0.1.0.1");
nRet = L_TwainInitSession2(&hSession, &appData, LTWAIN_INIT_MULTI_THREADED);
CheckRetCode(nRet);// the exception gets catched elsewhere but no error reported here
memset(pszTwnSourceName, 0, sizeof(pszTwnSourceName));
wcscpy(pszTwnSourceName, L"EPSON Artisan837/PX830"); // the name of the scanner is verifyed
sInfo.uStructSize = sizeof(LTWAINSOURCE);
sInfo.pszTwainSourceName = pszTwnSourceName;
CheckRetCode(L_TwainSelectSource(hSession, &sInfo)); // No error reported here
CheckRetCode(L_TwainStartCapsNeg(hSession)); // in here I get the return value -84 which is reported as "TWAIN DS or DSM reported error, app shouldn't (no need for your app to report the error)."
// the rest of the code but we cannot get there since above code reports error
}
Can anyone tell me what I'm doing wrong? Is there a step that I'm missing here?
EditThe function L_TwainSelectSource() make no effort to make sure the supplied source is valid and does not even return an error. As result, if you set the selected source to a garbage name, it will act as if it accepted it. From that point on if you try to Get/Set anything or try to acquire an image, every function returns -84.
Thank you
Sam
To test your code, I put the main window’s handle in a global variable:
globalhWnd = hWnd;
And modified your function to use that handle like this:
void OnThreadExecute(void *)
{
...
appData.hWnd = globalhWnd; // hWnd is valid handle of my main window
...
}
Then created a thread for it from the main program like this:
globalhWnd = hWnd;
_beginthread(OnThreadExecute, 0, 0);
I tried this with 5 different Twain sources: 2 virtual and 3 physical scanners (one of them an old Epson). All 5 drivers returned SUCCESS when calling L_TwainStartCapsNeg() from within the thread.
Two possibilities come to mind:
The problem might be caused by something else in your code other than the thread function.
Or the problem could be specific to your Twain driver.
To rule out the first possibility, I suggest creating a small test project that only creates a similar thread and does nothing else and trying it with different scanners. If it causes the same problem with all scanners, send that test project (not your full application) to support#leadtools.com and our support engineers with test it for you.
If the problem only happens with a specific Twain driver, try contacting the scanner’s vendor to see if they have an updated driver.

CreateEnclave VBS Attempt To Access Invalid Address

I'm trying to create a simple Enclave using Hyper-V's Virtualization-based Security that came out last year, however, I'm finding there to be a lack of documentation on MSDN. Due to that, I can't properly diagnose the issue I'm having.
Here the code that is failing for me with enclave == null giving me Attempt to access invalid address. And I'm not quite sure what it's failing to access.
if (IsEnclaveTypeSupported(ENCLAVE_TYPE_VBS))
{
DWORD lpError = 0;
ENCLAVE_CREATE_INFO_VBS vci = { 0 };
vci.Flags = 1;
PVOID enclave = CreateEnclave(GetCurrentProcess(),
NULL,
4096 * 2,
NULL,
ENCLAVE_TYPE_VBS,
&vci,
sizeof(ENCLAVE_CREATE_INFO_VBS),
&lpError);
if (enclave != NULL)
{
printf("Enclave created\n");
}
else
{
printf(GetLastErrorAsString().c_str());
}
}
else {
printf("VBS not supported\n");
}
Ok, I've solved it, it seems like dwSize has a minimum size, as well as it only working on even amounts of Mb.
For example 1Mb, 3Mb, 5Mb, etc. do not work, returning "Attempt to access invalid address." while 2Mb, 4Mb, 6Mb, etc. work as expected.

Right free of memory while using libudev

I use libudev to detect usb devices.
Initialise monitor and filter:
struct udev* udev = udev_new();
if (udev == nullptr) { /* error handling */ }
struct udev_monitor* usb = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(usb, "usb", NULL);
udev_monitor_enable_receiving(usb);
while(! canceled) { /* setup fd, poll fd, process result */ }
Then I release the allocated ressources with:
udev_monitor_unref(usb);
udev_unref(udev);
But sometimes I get
* glibc detected * ./usbtest: corrupted double-linked list: 0x084cc5d0 ***
I tried to use:
free(usb);
free(udev);
But then valgrind complaint about memory leaks.
What the right way to release the memory in this case?
According to the documentation it should be sufficient to use:
udev_unref(udev);
and here says:
udev_monitor_unref(usb);
should free that resource. If that gives you a double free, then something is not right, and you really need to debug that issue, not try to work around it by other means.

Setting up a chromium-like sandbox (error 0xc00000a5)

I'm trying to setup a sandbox akin to chromium. In particular, I'm trying to replicate their trick of creating a sleeping process with a low-privilege token, then setting a high-privilege token temporarily before running it. The idea is to let the process do all its initialization in high-privilege mode, then reverting to the low-privilege token right before running any unsafe code.
So far, I'm struggling just to get a basic test up and running. Here's my code:
#include "stdafx.h"
#include <atlbase.h>
#include <iostream>
#include <cassert>
#include <vector>
#include <string>
#include <AccCtrl.h>
#include <aclapi.h>
#define VERIFY(x) { bool r = x; assert(r); }
uint8_t* GetTokenInfo(const HANDLE& token, TOKEN_INFORMATION_CLASS info_class, DWORD* error)
{
// Get the required buffer size.
DWORD size = 0;
::GetTokenInformation(token, info_class, NULL, 0, &size);
if (!size)
{
*error = ::GetLastError();
return nullptr;
}
uint8_t* buffer = new uint8_t[size];
if (!::GetTokenInformation(token, info_class, buffer, size, &size))
{
*error = ::GetLastError();
return nullptr;
}
*error = ERROR_SUCCESS;
return buffer;
}
int main()
{
// Open the current token
CHandle processToken;
VERIFY(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &processToken.m_h));
// Create an impersonation token without restrictions
HANDLE impersonationToken;
VERIFY(DuplicateToken(processToken, SecurityImpersonation, &impersonationToken));
// Build the list of the deny only group SIDs
DWORD error;
uint8_t* buffer = GetTokenInfo(processToken, TokenGroups, &error);
if (!buffer) return error;
TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer);
std::vector<SID*> sids_for_deny_only;
for (unsigned int i = 0; i < token_groups->GroupCount; ++i)
{
if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0 &&
(token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0)
{
sids_for_deny_only.push_back(reinterpret_cast<SID*>(token_groups->Groups[i].Sid));
}
}
{
DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE;
uint8_t* buffer = new uint8_t[size];
TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(buffer);
BOOL result = ::GetTokenInformation(processToken, TokenUser, token_user, size, &size);
if (!result) return ::GetLastError();
sids_for_deny_only.push_back(reinterpret_cast<SID*>(token_user->User.Sid));
}
size_t deny_size = sids_for_deny_only.size();
SID_AND_ATTRIBUTES *deny_only_array = NULL;
if (deny_size)
{
deny_only_array = new SID_AND_ATTRIBUTES[deny_size];
for (unsigned int i = 0; i < sids_for_deny_only.size(); ++i)
{
deny_only_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY;
deny_only_array[i].Sid = const_cast<SID*>(sids_for_deny_only[i]);
}
}
// Create restricted sids
DWORD size_sid = SECURITY_MAX_SID_SIZE;
BYTE sid_[SECURITY_MAX_SID_SIZE];
VERIFY(::CreateWellKnownSid(WinNullSid, NULL, sid_, &size_sid));
SID_AND_ATTRIBUTES sidsToRestrict[] =
{
reinterpret_cast<SID*>(const_cast<BYTE*>(sid_)),
0
};
// Create the restricted token
HANDLE restrictedToken;
VERIFY(::CreateRestrictedToken(processToken,
0, // flags
deny_size,
deny_only_array,
0,
0,
_countof(sidsToRestrict), // number of SIDs to restrict,
sidsToRestrict, // no SIDs to restrict,
&restrictedToken));
VERIFY(::IsTokenRestricted(restrictedToken));
// Create a process using the restricted token (but keep it suspended)
STARTUPINFO startupInfo = { 0 };
PROCESS_INFORMATION processInfo;
VERIFY(::CreateProcessAsUser(restrictedToken,
L"C:\\Dev\\Projects\\SandboxTest\\Debug\\Naughty.exe",
0, // cmd line
0, // process attributes
0, // thread attributes
FALSE, // don't inherit handles
CREATE_SUSPENDED | DETACHED_PROCESS, // flags
0, // inherit environment
0, // inherit current directory
&startupInfo,
&processInfo));
// Set impersonation token with more rights
{
HANDLE temp_thread = processInfo.hThread;
if (!::SetThreadToken(&temp_thread, impersonationToken))
{
return 1;
}
}
// Run the process
if (!::ResumeThread(processInfo.hThread)) // Other process crashes immediately when this is run
{
return 1;
}
std::cout << "Done!" << std::endl;
return 0;
}
Not quite sure about deny list and restrict list yet, but if I understand this correctly it should be irrelevant. I'm calling SetThreadToken with my unrestricted token before running the thread, so I figure it should not matter what settings I use for restrictedToken. However, this is not the case; the new process crashes with the error code 0xc00000a5. If I use processToken instead of restrictedToken in CreateProcessAsUser, the code runs just fine. It's like SetThreadToken isn't doing anything.
I'm not doing much in naughty.exe right now, just starting an infinite loop.
Anyone know what I'm doing wrong here?
Edit 1:
According to this page, 0xc00000a5 means "STATUS_BAD_IMPERSONATION_LEVEL". Not sure on this, but I think I'm missing SeImpersonatePrivilege, causing stuff to fail. Still investigating options...
Edit 2:
Okay, seems like I had to reduce the privilege of the impersonation token to be able to use it with the other process. Not sure why, but not I can run the program without admin rights.
Still getting an error though :/ Now it's "STATUS_DLL_NOT_FOUND". Best lead from examining Process Monitor logs is an ACCESS DENIED on "C:\Windows\SysWOW64\ucrtbased.dll". The weird part is that it seems to be working once in a while (i.e. the spawned process sometimes runs just fine). Back to digging...
The problem is caused by the startup code trying to load the C runtime DLL from a new thread (which doesn't have access to the high-privilege token). What worked for me is to statically link the CRT into the sandbox process (i.e. /MTd in Debug builds, /MT in Release builds).
the new process crashes with the error code 0xc00000a5 / STATUS_BAD_IMPERSONATION_LEVEL
I've encountered this when:
the restricted token specifies SidsToRestrict that the permissive token does not.
the restricted token specifies a lower integrity level than the permissive token.
"restricted": The more restrictive token passed to CreateProcessAsUser
"permissive": The less restrictive token passed to SetThreadToken, and used until that thread calls RevertToSelf
It appears you cannot unrestrict sids or raise the integrity level with SetThreadToken, even if the parent process is unrestricted in these regards - you can only undeny sids, or unremove privileges.
I had to reduce the privilege of the impersonation token to be able to use it with the other process
This was a red herring for me. I tried every combination of keeping/removing privilige LUIDs for the permissive and restrictive tokens with little effect.
0xC0000135 / STATUS_DLL_NOT_FOUND
Best lead from examining Process Monitor logs is an ACCESS DENIED on "C:\Windows\SysWOW64\ucrtbased.dll". The weird part is that it seems to be working once in a while (i.e. the spawned process sometimes runs just fine).
The DLL loading/initializing code appears to be multithreaded. Speculating a bit here, my theory is that the new threads don't inherit the permissive token specified by SetThreadToken, and that ucrtbased.dll only loads successfully if the initial/main thread happened to be the thread that loaded ucrtbased.dll, and will fail if it's instead loaded by any of the worker threads - hence why it works sometimes, but typically not.
Workaround options:
Statically link the CRT. This is my current preference.
Don't pass Everyone or Users to SidsToDisable. Defeats the point of denying sids for sandboxing purposes in the first place, so I'd recommend against this, especially since I see no way to disable them later.
Have the parent process listen for CREATE_THREAD_DEBUG_EVENT and SetThreadToken for them too? I haven't tested this, but I suspect it might work. Would only want to do this during startup, lest you break open the sandbox after the child process has called RevertToSelf() etc.
0xC0000142 / ERROR_DLL_INIT_FAILED
Okay, this one's just me: I encountered this when trying to spawn a process at Untrusted integrity when initializing bcrypt.dll for Rust's stdlib. Spawn at Low instead, and have the child process lower itself to Untrusted post-init IMO.
How the heck do you use SidsToRestrict at all then?
You can't go from nullptr restrictions on a permissive token to real restrictions on a restricted token without causing 0xc00000a5 / STATUS_BAD_IMPERSONATION_LEVEL.
However, you can go from one restriction list to another, and neither necessairly needs to contain all the exact same SIDs as the other.
With a restrictive SidsToRestrict of only S-1-0-0 "NULL SID", I can use a permissive SidsToRestrict containing only:
S-1-1-0 "Everyone" (otherwise child dies w/ STATUS_ACCESS_DENIED)
S-1-5-5-x-yyyyyyy "LogonSessionId_..." (otherwise dies w/ STATUS_DLL_INIT_FAILED?)
Perhaps S-1-0-0 is considered a subset of Everyone, or perhaps the restricted sids can be outright disjoint?
Using all group SIDs marked SE_GROUP_ENABLED | SE_GROUP_LOGON_ID for your permissive token might be more appropriate.
Note that the child can't lower it's integrity level unless it can OpenProcessToken(.., ADJUST_DEFAULT, ..) based on the current access token.
The only overlap between the permissive token's restriction sids, and the restricted default TokenDefaultDacl, is the logon session, which doesn't grant write access by default:
ACCESS_ALLOWED_ACE { Mask: GENERIC_ALL, Sid: S-1-5-21-xxxx-yyyy-zzzz "%USERNAME%", .. }
ACCESS_ALLOWED_ACE { Mask: GENERIC_ALL, Sid: S-1-5-18 "SYSTEM", .. }
ACCESS_ALLOWED_ACE { Mask: GENERIC_READ | GENERIC_EXECUTE, Sid: S-1-5-5-x-yyyyyyyyy "LogonSessionId_x_yyyyyyyyy, .. }
So you may want to create a new default dacl for the restricted token with:
InitializeAcl(...);
AddAccessAllowedAce(acl, ACL_REVISION, TOKEN_ADJUST_DEFAULT | ..., logon_session_sid);
TOKEN_DEFAULT_DACL default_dacl = { acl };
SetTokenInformation(restriced, TokenDefaultDacl, &default_dacl, sizeof(default_dacl));
And ensure you adjust your child process's process token integrity level before calling RevertToSelf.

Why dynamically allocated buffer in user program makes kernel driver crash?

I have a program that allocates a buffer whose pointer is passed to a kernel driver through a custom IOCTL. In the driver I get an Mdl and lock the pages of the user-program buffer with "MmGetSystemAddressForMdlSafe" and then use the Mdl to fill the user program buffer.
If in the user program the buffer was a normal array, the driver always works as it should.
(WORD buffer[256], where word is an unsigned short)
If the user program buffer was instead allocated with the new keyword (WORD *buffer = new WORD[256]) or the malloc keyword (WORD *buffer=(WORD*) malloc(sizeof(*buffer)*256))) from time to time I get a BSOD and the error is "page fault in non paged area".
WHY?
Thanks!
EDIT(additional details):
In the driver I use MmGetSystemAddressForMdlSafe this way:
PVOID p_buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);
Irp is a PIRP that I receive as the second parameter when I handle the IRP_MJ_DEVICE_CONTROL MajorFunction.
After I've checked that p_buffer is not null, I use that pointer to write user buffer:
READ_PORT_BUFFER_USHORT((PUSHORT)(USHORT)current_port.address, (PUSHORT)p_buffer, 256)
IOCTL definition:
#define IOCTL_TEST_READPORT CTL_CODE(FILE_DEVICE_TEST, \
TEST_IOCTL_INDEX + 0, \
METHOD_OUT_DIRECT, \
FILE_ANY_ACCESS)
Driver function that handles IRP_MJ_DEVICE_CONTROL:
NTSTATUS TESTDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IrpStack;
ULONG input_buffer_size;
ULONG output_buffer_size;
ULONG control_code;
PVOID p_buffer;
NTSTATUS nt_status;
struct port current_port;
UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IrpStack = IoGetCurrentIrpStackLocation(Irp);
switch (IrpStack->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL:
control_code = IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (control_code)
{
case IOCTL_TEST_READPORT:
p_buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);
input_buffer_size = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
if (!p_buffer)
{
nt_status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
if (input_buffer_size)
{
memcpy (&current_port, Irp->AssociatedIrp.SystemBuffer, input_buffer_size);
switch (current_port.size)
{
case 1:
current_port.value = (ULONG)READ_PORT_UCHAR((PUCHAR)(USHORT)current_port.address);
memcpy (p_buffer, &current_port.value, sizeof(current_port.value));
Irp->IoStatus.Information = sizeof(current_port.value);
break;
case 0xF0:
READ_PORT_BUFFER_USHORT((PUSHORT)(USHORT)current_port.address, (PUSHORT)p_buffer, 256);
Irp->IoStatus.Information = sizeof(current_port.value);
break;
case 2:
current_port.value = (ULONG)READ_PORT_USHORT((PUSHORT)(USHORT)current_port.address);
memcpy (p_buffer, &current_port.value, sizeof(current_port.value));
Irp->IoStatus.Information = sizeof(current_port.value);
break;
}
}
else
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
break;
case IRP_MJ_CREATE:
KdPrint(("IRP_MJ_CREATE"));
break;
case IRP_MJ_CLOSE:
KdPrint(("IRP_MJ_CLOSE"));
break;
default:
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
break;
}
break;
}
nt_status = Irp->IoStatus.Status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return nt_status;
}
The relevant case is case 0xF0: inside case IOCTL_TEST_READPORT:
From my understanding you are misunderstanding the purpose of MmGetSystemAddressForMdlSafe. Per this document here, you use this function to get virtual addresses that are described by a MDL (Memory Descriptor List).
. If the driver must use virtual addresses to access the pages described by the MDL, it must map those pages into the system address space using MmGetSystemAddressForMdlSafe
that same document also says this:
To use virtual addresses to access the buffer described by the MDL, the driver calls MmGetSystemAddressForMdlSafe to map the buffer into system space.
MmGetSystemAddressForMdlSafe:
Maps the physical pages described by an MDL into system space and returns a virtual address for the MDL. The returned virtual address can be used at any IRQL and in any process context.
If you look at the MSDN documentation of MmGetSystemAddressForMdlSafe then you will see this following line:
The MmGetSystemAddressForMdlSafe macro returns a nonpaged system-space virtual address for the buffer that the specified MDL describes.
It says that this functions returns a non-paged virtual address for a buffer that is described by an MDL.
The definition of MDL is the following:
A memory descriptor list (MDL) describes a list of pages in physical memory.
This is a description of pages in physical memory, not virtual memory. Your buffer allocated by new will have virtual addresses already, trying to use MmGetSystemAddressForMdlSafe on it, is wrong. You should use that function to get a virtual address from an MDL, not a MDL from a virtual address range.
Now, moving on to an explanation for the page fault in non-paged area:
Now if you think about it, it is likely that your buffer allocated by new or malloc is already in a paged memory area (in fact, seeing that it's in user land, it's extremely likely), meaning that trying to get a virtual address to this buffer (which is already wrong because it's not an MDL), will cause a page fault in non-paged area, because the memory of the buffer is in a paged area, whereas you're mapping it to a non-paged area in the kernel, and non-paged areas can not cause a page fault. (most likely will have to do with wrong IRQL levels)