delphidelphi-10.3-riodeviceiocontrol

DeviceIoControl - GetLastError: ERROR_NOACCESS - 998


I have a kernel driver written in C, where it is expecting a text of type PCWSTR. What's the Delphi type equivalent to send a control code? I tried sending using the following code but GetLastError reports ERROR_NOACCESS. How to solve that?

program Driverloader;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows,
  WinSvc,
  SysUtils;

function InstallAndStartDriver(DriverPath, DriverName: WideString; out DriverDevice: THandle): Boolean;
var
  hSCManager, hService: THandle;
  lpServiceArgVectors: PWideChar;
begin
  Result := False;
  hSCManager := 0;
  hSCManager := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
  if hSCManager <> 0 then
  begin
    try
      Writeln('OpenSCManagerW() - OK');
      hService := 0;
      hService := CreateServiceW(hSCManager, PWideChar(DriverName), PWideChar(DriverName), SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, PWideChar(DriverPath), nil, nil, nil, nil, nil);
      hService := 0;
      lpServiceArgVectors := nil;
      hService := OpenServiceW(hSCManager, PWideChar(DriverName), SERVICE_ALL_ACCESS);
      if hService <> 0 then
      begin
        try
          Writeln('OpenServiceW() - OK');
          if StartServiceW(hService, 0, PWideChar(lpServiceArgVectors)) then
          begin
            Writeln('StartServiceW() - OK');
            Result := True;
          end;
        finally
          CloseServiceHandle(hService);
        end;
      end;
    finally
      CloseServiceHandle(hSCManager);
    end;
  end;
  if Result the
  begin
    DriverDevice := CreateFileW(PWideChar('\\.\' + DriverName), GENERIC_READ or GENERIC_WRITE, 0, PSECURITY_DESCRIPTOR(nil), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    Result := GetLastError() = ERROR_SUCCESS;
    Writeln('CreateFileW() - ' + IntToStr(GetLastError));
  end;
end;

function CTL_CODE(DeviceType, _Function, Method, Access: Cardinal): Cardinal;
begin
  Result := (DeviceType shl 16) or (Access shl 14) or (_Function shl 2) or (Method);
end;

var
  driver: THandle;
  BytesReturned, IOCTL_PATH_DELETE: Cardinal;
  szInput, szOutput: array[0..255] of WideChar;
begin
  try
    IOCTL_PATH_DELETE := CTL_CODE(FILE_DEVICE_UNKNOWN, $500, METHOD_BUFFERED, FILE_ANY_ACCESS);

    lstrcpy(szInput, '\??\C:\Program Files\Software Folder');

    if InstallAndStartDriver(IncludeTrailingPathDelimiter(GetCurrentDir) + 'MyDriver.sys', 'MyDriver', driver) then
    begin
      Writeln('InstallAndStartDriver() - OK');
      Sleep(2000);

      if not DeviceIOControl(driver, IOCTL_PATH_DELETE, PWideChar(szInput[0]), SizeOf(szInput), PWideChar(szOutput[0]), SizeOf(szOutput) * MAXBYTE, BytesReturned, nil) then
        Writeln('DeviceIOControl() - Error: ' + IntToStr(GetLastError))
      else
        Writeln('Success! - ' + szOutput);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

Edit

Receiving text on kernel driver "Dispatch" method:

NTSTATUS DrvDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION irpStack;
    PVOID ioBuffer;
    ULONG ioControlCode;
    NTSTATUS ntStatus;
    PCWSTR Path;

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;

    irpStack = IoGetCurrentIrpStackLocation(Irp);

    ioBuffer = Irp->AssociatedIrp.SystemBuffer;

    switch (irpStack->MajorFunction) {

        case IRP_MJ_DEVICE_CONTROL:

            ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

            switch (ioControlCode) {

                case IOCTL_PATH_DELETE: {
                    Path = *(PCWSTR*)ioBuffer; // <-- fails and stop here

                    dprintf("%s\n", Path);

                    break;
                }

                default:
                    Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

                    break;
            }

            break;
    }

    ntStatus = Irp->IoStatus.Status;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return ntStatus;
}

Solution

  • While calling DeviceIOControl(IOCTL_PATH_DELETE), when you pass in szInput and szOutput, you are type-casting a single WideChar to a PWideChar, so whatever numeric value that WideChar contains will be mis-interpreted as a memory address, which is wrong. So you end up passing in invalid memory addresses, which can easily account for the ERROR_NOACCESS error.

    Change PWideChar(szInput[0]) to PWideChar(@szInput[0]) or simply get rid of the type-cast altogether, passing @szInput as-is. Same with szOutput.

    if not DeviceIOControl(driver, IOCTL_PATH_DELETE, @szInput, SizeOf(szInput), @szOutput, SizeOf(szOutput), BytesReturned, nil) then
    

    Also, your use of GetLastError() on CreateFileW() is wrong. The return value of GetLastError() is indeterminate and not valid to use unless CreateFileW() returns INVALID_HANDLE_VALUE, OR if you use dwCreationDisposition=CREATE_ALWAYS or dwCreationDisposition=OPEN_ALWAYS, neither of which you are using.

    DriverDevice := CreateFileW(...);
    Result := DriverDevice <> INVALID_HANDLE_VALUE;
    if not Result then
      Writeln('CreateFileW() - Error: ' + IntToStr(GetLastError))
    else
      Writeln('CreateFileW() - Success!');
    

    UPDATE: your kernel driver is expecting a pointer to a pointer to a null-terminated wide string. But your Delphi code is passing in a pointer to a null-terminated string. That is why your kernel code is crashing. You need to remove the unnecessary level of indirection in the kernel code:

    //Path = *(PCWSTR*)ioBuffer;
    Path = (PCWSTR)ioBuffer;
    

    Also, your call to dprintf() is expecting a narrow string, but you are passing it a wide string. To print a wide string, you need to use %S instead of %s, eg:

    dprintf("%S\n", Path); 
    

    On a side note, your Delphi code is leaking the handle returned by CreateServiceW(). You need to call CloseServiceHandle():

    hService := CreateServiceW(...);
    if hService <> 0 then
      CloseServiceHandle(hService); // <-- ADD THIS!
    lpServiceArgVectors := nil;
    hService := OpenServiceW(...);
    if hService <> 0 then
    begin
     ...
    end;
    

    However, there is no good reason to immediately close a created service just to re-open it again. Use the handle that CreateServiceW() gives you:

    hService := CreateServiceW(...);
    lpServiceArgVectors := nil;
    if hService <> 0 then
    begin
     ...
    end;