
/**
 * @file: pushlock.c
 *
 * Pushlocky jsou nedokumentovana synchronizacni primitiva, ktera pouziva jadro Windows
 * od verze XP stale vice a vice. Jedna se o zamky reader-writer ne nepodobne zamkum
 * SRWL (Slim Reader Writer Locks) z uzivatelskeho rezimu Vista/7. Velikost je rovna
 * delce adresy. 
 *
 * Tento soubor dovoluje i vyvojarum ovladacu tato primitiva vyuzivat. Zaroven je
 * kompatibilni i s Windows 2000, protoze adresy jednotlivych rutin pro praci s pushlocky
 * nacita dynamicky pres MmGetSystemRoutineAddress.
 */
#include <ntddk.h>
#include "version.h"
#include "pushlock.h"

// Definice jednotlivych rutin pro praci s pushlocky tak, jak jsou zapsany v ntoskrnl.exe

// Inicializace zamku
typedef VOID (*EXINITIALIZEPUSHLOCK)(PVOID *Lock);
// Zamknout pro exklizivni pristup
typedef VOID (FASTCALL *EXFACQUIREPUSHLOCKEXCLUSIVE)(PVOID *Lock);
// Zamknout pro sdileny pristup
typedef VOID (FASTCALL *EXFACQUIREPUSHLOCKSHARED)(PVOID *Lock);
// Odemknout sdileny pristup
typedef VOID (FASTCALL *EXFRELEASEPUSHLOCKSHARED)(PVOID *Lock);
// Odemknout exkluzivni pristup
typedef VOID (FASTCALL *EXFRELEASEPUSHLOCKEXCLUSIVE)(PVOID *Lock);
// Odemknuti zamku zamceneho pro libovolny pristup.
typedef VOID (FASTCALL *EXFRELEASEPUSHLOCK)(PVOID *Lock);

// Promenne pro uchovani adres rutin pro praci s pushlocky v ntoskrnl.exe
static EXINITIALIZEPUSHLOCK _ExInitializePushLock = NULL;
static EXFACQUIREPUSHLOCKSHARED _ExfAcquirePushLockShared = NULL;
static EXFACQUIREPUSHLOCKEXCLUSIVE _ExfAcquirePushLockExclusive = NULL;
static EXFRELEASEPUSHLOCKSHARED _ExfReleasePushLockShared = NULL;
static EXFRELEASEPUSHLOCKEXCLUSIVE _ExfReleasePushLockExclusive = NULL;
static EXFRELEASEPUSHLOCK _ExfReleasePushLock = NULL;


/** Vrati adresu zadane rutiny.
 *
 * Prohledavaji se pouze moduly ntoskrnl.exe a hal.dll.
 *
 * @param RoutineName Retezec obsahujici nazev hledane rutiny.
 * 
 * @return Funkce vraci adresu hledane rutiny. Pokud rutina daneho jmena neexistuje ani
 * v ntoskrnl.exe ani v hal.dll, funkce vraci NULL.
 */
static PVOID _GetRoutineAddress(PWCHAR RoutineName)
{
  PVOID Ret = NULL;
  UNICODE_STRING uName;

  RtlInitUnicodeString(&uName, RoutineName);
  Ret = MmGetSystemRoutineAddress(&uName);

  return Ret;
}


/* 
 * Nasleduji "proxy" funkce, ktere obaluji jednotliva volani rutin pro praci
 * s pushlocky v ntoskrnl.exe. Ne vsechny rutiny pro praci s pushlocky jsou definovany
 * na vsech verzich Windows (XP, 2003, Vista, 7), a tak se nektere proxy funkce pouzivaji
 * jen na nekterych verzich OS.
 */

 
/** Inicializuje pushlock.
 *
 * @param Lock adresa promenne, ktera ma byt inicializovana jako pushlock.
 */
VOID PushLockInitialize(PVOID *Lock)
{
  _ExInitializePushLock(Lock);

  return;
}


/** Zamkne pushlock pro sdileny pristup.
 *
 * Rutina taky zakaze normalni APC jadra vstoupenim do kritickeho regionu.
 *
 * @param Lock Adresa pushlocku, ktery si volajici preje zamknout pro sdileny pristup.
 */
VOID PushLockAcquireShared(PVOID *Lock)
{
  KeEnterCriticalRegion();
  _ExfAcquirePushLockShared(Lock);

  return;
}


/** Zamkne pushlock pro exkluzivni pristup.
 *
 * Rutina taky zakaze normalni APC jadra vstoupenim do kritickeho regionu.
 *
 * @param Lock Adresa pushlocku, ktery si volajici preje zamknout pro exkluzivni pristup.
 */
VOID PushLockAcquireExclusive(PVOID *Lock)
{
  KeEnterCriticalRegion();
  _ExfAcquirePushLockExclusive(Lock);

  return;
}

/** Odemkne pushlock zamceny pro sdileny pristup.
 *
 * Rutina zaroven povoli normalni APC jadra vystoupenim z kritickeho regionu.
 *
 * @param Lock Adresa pushlocku, ktery chce volajici odemknout.
 */
VOID PushLockReleaseShared(PVOID *Lock)
{
  _ExfReleasePushLockShared(Lock);
  KeLeaveCriticalRegion();

  return;
}


/** Odemkne pushlock zamceny pro exkluzivni pristup.
 *
 * Rutina zaroven povoli normalni APC jadra vystoupenim z kritickeho regionu.
 *
 * @param Lock Adresa pushlocku, ktery chce volajici odemknout.
 */
 VOID PushLockReleaseExclusive(PVOID *Lock)
{
  _ExfReleasePushLockExclusive(Lock);
  KeLeaveCriticalRegion();

  return;
}


/** Odemkne pushlock. Pushlock muze byt zamcen pro libovolny pristup.
 *
 * Rutina zaroven povoli normalni APC jadra vystoupenim z kritickeho regionu.
 *
 * #param Lock Adresa pushlocku, ktery si volajici preje odemknout.
 */
VOID PushLockRelease(PVOID *Lock)
{
  _ExfReleasePushLock(Lock);
  KeLeaveCriticalRegion();

  return;
}

/*
 * Nasledujici funkce jsou nahrazky za rutiny, ktere na starsich Windows (XP, 2K3)
 * jeste neexistuji. Jejich vyznam je stejny jako v rutinach popsanych vyse.
 */

static VOID WXPW2K3ExInitializePushLock(PVOID *Lock)
{
  *Lock = NULL;

  return;
}

static VOID FASTCALL WXPExfReleasePushLockShared(PVOID *Lock)
{
  _ExfReleasePushLock(Lock);

  return;
}

static VOID FASTCALL WXPExfReleasePushLockExclusive(PVOID *Lock)
{
  _ExfReleasePushLock(Lock);

  return;
}


/** Provadi inicializaci modulu pro praci s pushlocky.
 *
 * Rutina nejprve zjisti aktualni verzi operacniho systemu. Pokud se nejedna o 
 * Windows 2000, rutina namapuje jednotlive funkce na adresy v ntoskrnl.exe. Mapovani
 * se napric verzemi Windows muze lisit, protoze kazda nova verze podporuje vetsi
 * mnozstvi funkci pro praci s pushlocky.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech operace.
 */
NTSTATUS PushLockModuleInit(VOID)
{
  LONG VersionCode = 0;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("pushlock.c: PushLockModuleInit()\n"));

  VersionCode = GetVersionCode();
  if (VersionCode != VERSION_UNKNOWN)
    Status = STATUS_SUCCESS;

  if (NT_SUCCESS(Status) && VersionCode != VERSION_W2K) {
	_ExfAcquirePushLockShared = (EXFACQUIREPUSHLOCKSHARED)_GetRoutineAddress(L"ExfAcquirePushLockShared");
    _ExfAcquirePushLockExclusive = (EXFACQUIREPUSHLOCKEXCLUSIVE)_GetRoutineAddress(L"ExfAcquirePushLockExclusive");
	_ExfReleasePushLock = (EXFRELEASEPUSHLOCK)_GetRoutineAddress(L"ExfReleasePushLock");

	if (VersionCode != VERSION_WXP && VersionCode != VERSION_W2K3)
	  _ExInitializePushLock = (EXINITIALIZEPUSHLOCK)_GetRoutineAddress(L"ExInitializePushLock");
	else _ExInitializePushLock = WXPW2K3ExInitializePushLock;
	
	if (VersionCode != VERSION_WXP) {
	  _ExfReleasePushLockShared = (EXFRELEASEPUSHLOCKSHARED)_GetRoutineAddress(L"ExfReleasePushLockShared");
      _ExfReleasePushLockExclusive = (EXFRELEASEPUSHLOCKEXCLUSIVE)_GetRoutineAddress(L"ExfReleasePushLockExclusive");
	} else {
	  _ExfReleasePushLockShared = WXPExfReleasePushLockShared;
	  _ExfReleasePushLockExclusive = WXPExfReleasePushLockExclusive;
	}

    if (_ExInitializePushLock == NULL || _ExfAcquirePushLockShared == NULL ||
	    _ExfAcquirePushLockExclusive == NULL || _ExfReleasePushLockShared == NULL ||
	    _ExfReleasePushLockExclusive == NULL || _ExfReleasePushLock == NULL) {
      Status = STATUS_NOT_FOUND;
      if (_ExInitializePushLock == NULL)
        KdPrint(("pushlock.c: PushLockModuleInit(): Failed to find nt!ExInitializePushLock\n"));

      if (_ExfAcquirePushLockShared == NULL)
        KdPrint(("pushlock.c: PushLockModuleInit(): Failed to find nt!ExfAcquirePushLockShared\n"));

      if (_ExfAcquirePushLockExclusive == NULL)
        KdPrint(("pushlock.c: PushLockModuleInit(): Failed to find nt!ExfAcquirePushLockExclusive\n"));

      if (_ExfReleasePushLockShared == NULL)
        KdPrint(("pushlock.c: PushLockModuleInit(): Failed to find nt!ExfReleasePushLockShared\n"));

      if (_ExfReleasePushLockExclusive == NULL)
        KdPrint(("pushlock.c: PushLockModuleInit(): Failed to find nt!ExfReleasePushLockExclusive\n"));

      if (_ExfReleasePushLock == NULL)
        KdPrint(("pushlock.c: PushLockModuleInit(): Failed to find nt!ExfReleasePushLock\n"));
    }
  }
  KdPrint(("pushlock.c: PushLockModuleInit(-):0x%x\n", Status));
  return Status;
}


/** Provadi uklid po modulu pracujiciho s pushlocky.
 *
 * Jelikoz tento modul nezasahuje nijak do internich struktur jadra, neni co uklizet. 
 * Funkce tedy nedela nic.
 */
VOID PushLockModuleFinit(VOID)
{
  KdPrint(("pushlock.c: PushLockModuleFinit()\n"));

  KdPrint(("pushlock.c: PushLockModuleFinit(-)\n"));
  return;
}
