Bug 1445025: Part 4 - Add header for Native NT facilities; r=mhowell
authorAaron Klotz <aklotz@mozilla.com>
Tue, 05 Jun 2018 15:19:30 -0600
changeset 805576 b12029c4cbf21226e60f9097f2f827954cd982b6
parent 805575 92ef8aaaa0f24280431472b11c611461104e41cd
child 805577 64936071bf7710033436a4c5141057fad3d3685b
push id112702
push userbmo:mh+mozilla@glandium.org
push dateFri, 08 Jun 2018 01:32:47 +0000
reviewersmhowell
bugs1445025
milestone62.0a1
Bug 1445025: Part 4 - Add header for Native NT facilities; r=mhowell
browser/app/winlauncher/NativeNt.h
browser/app/winlauncher/moz.build
browser/app/winlauncher/test/TestNativeNt.cpp
browser/app/winlauncher/test/moz.build
new file mode 100644
--- /dev/null
+++ b/browser/app/winlauncher/NativeNt.h
@@ -0,0 +1,546 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_NativeNt_h
+#define mozilla_NativeNt_h
+
+#if defined(MOZILLA_INTERNAL_API)
+#error "This header is for initial process initialization. You don't want to be including this here."
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include <stdint.h>
+#include <windows.h>
+#include <winnt.h>
+#include <winternl.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+
+extern "C" {
+
+#if !defined(STATUS_ACCESS_DENIED)
+#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
+#endif // !defined(STATUS_ACCESS_DENIED)
+
+#if !defined(STATUS_DLL_NOT_FOUND)
+#define STATUS_DLL_NOT_FOUND ((NTSTATUS)0xC0000135L)
+#endif // !defined(STATUS_DLL_NOT_FOUND)
+
+enum SECTION_INHERIT
+{
+  ViewShare = 1,
+  ViewUnmap = 2
+};
+
+NTSTATUS NTAPI
+NtMapViewOfSection(HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress,
+                   ULONG_PTR aZeroBits, SIZE_T aCommitSize,
+                   PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
+                   SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
+                   ULONG aProtectionFlags);
+
+NTSTATUS NTAPI
+NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
+
+enum MEMORY_INFORMATION_CLASS
+{
+  MemoryBasicInformation = 0,
+  MemorySectionName = 2
+};
+
+// NB: When allocating, space for the buffer must also be included
+typedef struct _MEMORY_SECTION_NAME
+{
+  UNICODE_STRING mSectionFileName;
+} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME;
+
+NTSTATUS NTAPI
+NtQueryVirtualMemory(HANDLE aProcess, PVOID aBaseAddress,
+                     MEMORY_INFORMATION_CLASS aMemInfoClass, PVOID aMemInfo,
+                     SIZE_T aMemInfoLen, PSIZE_T aReturnLen);
+
+LONG NTAPI
+RtlCompareUnicodeString(PCUNICODE_STRING aStr1, PCUNICODE_STRING aStr2,
+                        BOOLEAN aCaseInsensitive);
+
+BOOLEAN NTAPI
+RtlEqualUnicodeString(PCUNICODE_STRING aStr1, PCUNICODE_STRING aStr2,
+                      BOOLEAN aCaseInsensitive);
+
+NTSTATUS NTAPI
+RtlGetVersion(PRTL_OSVERSIONINFOW aOutVersionInformation);
+
+PVOID NTAPI
+RtlAllocateHeap(PVOID aHeapHandle, ULONG aFlags, SIZE_T aSize);
+
+PVOID NTAPI
+RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem, SIZE_T aNewSize);
+
+BOOLEAN NTAPI
+RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
+
+VOID NTAPI
+RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
+
+VOID NTAPI
+RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
+
+} // extern "C"
+
+namespace mozilla {
+namespace nt {
+
+struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME
+{
+  MemorySectionNameBuf()
+  {
+    mSectionFileName.Length = 0;
+    mSectionFileName.MaximumLength = sizeof(mBuf);
+    mSectionFileName.Buffer = mBuf;
+  }
+
+  WCHAR mBuf[MAX_PATH];
+};
+
+inline bool
+FindCharInUnicodeString(const UNICODE_STRING& aStr, WCHAR aChar, uint16_t& aPos,
+                        uint16_t aStartIndex = 0)
+{
+  const uint16_t aMaxIndex = aStr.Length / sizeof(WCHAR);
+
+  for (uint16_t curIndex = aStartIndex; curIndex < aMaxIndex; ++curIndex) {
+    if (aStr.Buffer[curIndex] == aChar) {
+      aPos = curIndex;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+inline bool
+IsHexDigit(WCHAR aChar)
+{
+  return aChar >= L'0' && aChar <= L'9' ||
+         aChar >= L'A' && aChar <= L'F' ||
+         aChar >= L'a' && aChar <= L'f';
+}
+
+inline bool
+MatchUnicodeString(const UNICODE_STRING& aStr, bool (*aPredicate)(WCHAR))
+{
+  WCHAR* cur = aStr.Buffer;
+  WCHAR* end = &aStr.Buffer[aStr.Length / sizeof(WCHAR)];
+  while (cur < end) {
+    if (!aPredicate(*cur)) {
+      return false;
+    }
+
+    ++cur;
+  }
+
+  return true;
+}
+
+inline bool
+Contains12DigitHexString(const UNICODE_STRING& aLeafName)
+{
+  uint16_t start, end;
+  if (!FindCharInUnicodeString(aLeafName, L'.', start)) {
+    return false;
+  }
+
+  ++start;
+  if (!FindCharInUnicodeString(aLeafName, L'.', end, start)) {
+    return false;
+  }
+
+  if (end - start != 12) {
+    return false;
+  }
+
+  UNICODE_STRING test;
+  test.Buffer = &aLeafName.Buffer[start];
+  test.Length = (end - start) * sizeof(WCHAR);
+  test.MaximumLength = test.Length;
+
+  return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline bool
+IsFileNameAtLeast16HexDigits(const UNICODE_STRING& aLeafName)
+{
+  uint16_t dotIndex;
+  if (!FindCharInUnicodeString(aLeafName, L'.', dotIndex)) {
+    return false;
+  }
+
+  if (dotIndex < 16) {
+    return false;
+  }
+
+  UNICODE_STRING test;
+  test.Buffer = aLeafName.Buffer;
+  test.Length = dotIndex * sizeof(WCHAR);
+  test.MaximumLength = aLeafName.MaximumLength;
+
+  return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline void
+GetLeafName(PUNICODE_STRING aDestString, PCUNICODE_STRING aSrcString)
+{
+  WCHAR* buf = aSrcString->Buffer;
+  WCHAR* end = &aSrcString->Buffer[(aSrcString->Length / sizeof(WCHAR)) - 1];
+  WCHAR* cur = end;
+  while (cur >= buf) {
+    if (*cur == L'\\') {
+      break;
+    }
+
+    --cur;
+  }
+
+  // At this point, either cur points to the final backslash, or it points to
+  // buf - 1. Either way, we're interested in cur + 1 as the desired buffer.
+  aDestString->Buffer = cur + 1;
+  aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR);
+  aDestString->MaximumLength = aDestString->Length;
+}
+
+inline char
+EnsureLowerCaseASCII(char aChar)
+{
+  if (aChar >= 'A' && aChar <= 'Z') {
+    aChar -= 'A' - 'a';
+  }
+
+  return aChar;
+}
+
+inline int
+StricmpASCII(const char* aLeft, const char* aRight)
+{
+  char curLeft, curRight;
+
+  do {
+    curLeft = EnsureLowerCaseASCII(*(aLeft++));
+    curRight = EnsureLowerCaseASCII(*(aRight++));
+  } while(curLeft && curLeft == curRight);
+
+  return curLeft - curRight;
+}
+
+class MOZ_RAII PEHeaders final
+{
+  /**
+   * This structure is documented on MSDN as VS_VERSIONINFO, but is not present
+   * in SDK headers because it cannot be specified as a C struct. The following
+   * structure contains the fixed-length fields at the beginning of
+   * VS_VERSIONINFO.
+   */
+  struct VS_VERSIONINFO_HEADER
+  {
+    WORD             wLength;
+    WORD             wValueLength;
+    WORD             wType;
+    WCHAR            szKey[16]; // ArrayLength(L"VS_VERSION_INFO")
+    // Additional data goes here, aligned on a 4-byte boundary
+  };
+
+public:
+  explicit PEHeaders(void* aBaseAddress)
+    : PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress))
+  {
+  }
+
+  // The lowest two bits of an HMODULE are used as flags. Stripping those bits
+  // from the HMODULE yields the base address of the binary's memory mapping.
+  // (See LoadLibraryEx docs on MSDN)
+  explicit PEHeaders(HMODULE aModule)
+    : PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(
+                  reinterpret_cast<uintptr_t>(aModule) & ~uintptr_t(3)))
+  {
+  }
+
+  explicit operator bool() const
+  {
+    return !!mImageLimit;
+  }
+
+  /**
+   * This overload computes absolute virtual addresses relative to the base
+   * address of the binary.
+   */
+  template <typename T, typename R>
+  T RVAToPtr(R aRva)
+  {
+    return RVAToPtr<T>(mMzHeader, aRva);
+  }
+
+  /**
+   * This overload computes a result by adding aRva to aBase, but also ensures
+   * that the resulting pointer falls within the bounds of this binary's memory
+   * mapping.
+   */
+  template <typename T, typename R>
+  T RVAToPtr(void* aBase, R aRva)
+  {
+    if (!mImageLimit) {
+      return nullptr;
+    }
+
+    char* absAddress = reinterpret_cast<char*>(aBase) + aRva;
+    if (absAddress < reinterpret_cast<char*>(mMzHeader) ||
+        absAddress > reinterpret_cast<char*>(mImageLimit)) {
+      return nullptr;
+    }
+
+    return reinterpret_cast<T>(absAddress);
+  }
+
+  PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory()
+  {
+    return GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR>(
+      IMAGE_DIRECTORY_ENTRY_IMPORT);
+  }
+
+  PIMAGE_RESOURCE_DIRECTORY GetResourceTable()
+  {
+    return GetImageDirectoryEntry<PIMAGE_RESOURCE_DIRECTORY>(
+      IMAGE_DIRECTORY_ENTRY_RESOURCE);
+  }
+
+  bool
+  GetVersionInfo(uint64_t& aOutVersion)
+  {
+    // RT_VERSION == 16
+    // Version resources require an id of 1
+    auto root = FindResourceLeaf<VS_VERSIONINFO_HEADER*>(16, 1);
+    if (!root) {
+      return false;
+    }
+
+    VS_FIXEDFILEINFO* fixedInfo = GetFixedFileInfo(root);
+    if (!fixedInfo) {
+      return false;
+    }
+
+    aOutVersion = ((static_cast<uint64_t>(fixedInfo->dwFileVersionMS) << 32) |
+                   static_cast<uint64_t>(fixedInfo->dwFileVersionLS));
+    return true;
+  }
+
+  bool
+  GetTimeStamp(DWORD& aResult)
+  {
+    if (!(*this)) {
+      return false;
+    }
+
+    aResult = mPeHeader->FileHeader.TimeDateStamp;
+    return true;
+  }
+
+  PIMAGE_IMPORT_DESCRIPTOR
+  GetIATForModule(const char* aModuleNameASCII)
+  {
+    for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+         IsValid(curImpDesc); ++curImpDesc) {
+      auto curName = RVAToPtr<const char*>(curImpDesc->Name);
+      if (!curName) {
+        return nullptr;
+      }
+
+      if (StricmpASCII(aModuleNameASCII, curName)) {
+        continue;
+      }
+
+      // curImpDesc now points to the IAT for the module we're interested in
+      return curImpDesc;
+    }
+
+    return nullptr;
+  }
+
+  /**
+   * Resources are stored in a three-level tree. To locate a particular entry,
+   * you must supply a resource type, the resource id, and then the language id.
+   * If aLangId == 0, we just resolve the first entry regardless of language.
+   */
+  template <typename T> T
+  FindResourceLeaf(WORD aType, WORD aResId, WORD aLangId = 0)
+  {
+    PIMAGE_RESOURCE_DIRECTORY topLevel = GetResourceTable();
+    if (!topLevel) {
+      return nullptr;
+    }
+
+    PIMAGE_RESOURCE_DIRECTORY_ENTRY typeEntry = FindResourceEntry(topLevel,
+                                                                  aType);
+    if (!typeEntry || !typeEntry->DataIsDirectory) {
+      return nullptr;
+    }
+
+    auto idDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(topLevel,
+                                                     typeEntry->OffsetToDirectory);
+    PIMAGE_RESOURCE_DIRECTORY_ENTRY idEntry = FindResourceEntry(idDir, aResId);
+    if (!idEntry || !idEntry->DataIsDirectory) {
+      return nullptr;
+    }
+
+    auto langDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(topLevel,
+                                                       idEntry->OffsetToDirectory);
+    PIMAGE_RESOURCE_DIRECTORY_ENTRY langEntry;
+    if (aLangId) {
+      langEntry = FindResourceEntry(langDir, aLangId);
+    } else {
+      langEntry = FindFirstResourceEntry(langDir);
+    }
+
+    if (!langEntry || langEntry->DataIsDirectory) {
+      return nullptr;
+    }
+
+    auto dataEntry = RVAToPtr<PIMAGE_RESOURCE_DATA_ENTRY>(topLevel,
+                                                          langEntry->OffsetToData);
+    return RVAToPtr<T>(dataEntry->OffsetToData);
+  }
+
+  static bool IsValid(PIMAGE_IMPORT_DESCRIPTOR aImpDesc)
+  {
+    return aImpDesc && aImpDesc->OriginalFirstThunk != 0;
+  }
+
+  static bool IsValid(PIMAGE_THUNK_DATA aImgThunk)
+  {
+    return aImgThunk && aImgThunk->u1.Ordinal != 0;
+  }
+
+private:
+  explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader)
+    : mMzHeader(aMzHeader)
+    , mPeHeader(nullptr)
+    , mImageLimit(nullptr)
+  {
+    if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+      return;
+    }
+
+    mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew);
+    if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) {
+      return;
+    }
+
+    mImageLimit =
+      RVAToPtrUnchecked<void*>(mPeHeader->OptionalHeader.SizeOfImage - 1UL);
+  }
+
+  template <typename T>
+  T GetImageDirectoryEntry(unsigned int aDirectoryIndex)
+  {
+    if (aDirectoryIndex >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) {
+      return nullptr;
+    }
+
+    IMAGE_DATA_DIRECTORY& dirEntry =
+      mPeHeader->OptionalHeader.DataDirectory[aDirectoryIndex];
+    return RVAToPtr<T>(dirEntry.VirtualAddress);
+  }
+
+  // This private overload does not have bounds checks, because we need to be
+  // able to resolve the bounds themselves.
+  template <typename T, typename R>
+  T RVAToPtrUnchecked(R aRva)
+  {
+    return reinterpret_cast<T>(reinterpret_cast<char*>(mMzHeader) + aRva);
+  }
+
+  PIMAGE_RESOURCE_DIRECTORY_ENTRY
+  FindResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel, WORD aId)
+  {
+    // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+    // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. Since this function
+    // searches by ID, we need to skip past any named entries before iterating.
+    auto dirEnt =
+      reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1) +
+      aCurLevel->NumberOfNamedEntries;
+    for (WORD i = 0; i < aCurLevel->NumberOfIdEntries; ++i) {
+      if (dirEnt[i].Id == aId) {
+        return &dirEnt[i];
+      }
+    }
+
+    return nullptr;
+  }
+
+  PIMAGE_RESOURCE_DIRECTORY_ENTRY
+  FindFirstResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel)
+  {
+    // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+    // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. We just return the first
+    // entry, regardless of whether it is indexed by name or by id.
+    auto dirEnt = reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1);
+    WORD numEntries = aCurLevel->NumberOfNamedEntries +
+                      aCurLevel->NumberOfIdEntries;
+    if (!numEntries) {
+      return nullptr;
+    }
+
+    return dirEnt;
+  }
+
+  VS_FIXEDFILEINFO*
+  GetFixedFileInfo(VS_VERSIONINFO_HEADER* aVerInfo)
+  {
+    WORD length = aVerInfo->wLength;
+    WORD offset = sizeof(VS_VERSIONINFO_HEADER);
+    if (!offset) {
+      return nullptr;
+    }
+
+    const wchar_t kVersionInfoKey[] = L"VS_VERSION_INFO";
+    if (::RtlCompareMemory(aVerInfo->szKey, kVersionInfoKey,
+                           ArrayLength(kVersionInfoKey)) !=
+        ArrayLength(kVersionInfoKey)) {
+      return nullptr;
+    }
+
+    uintptr_t base = reinterpret_cast<uintptr_t>(aVerInfo);
+    // Align up to 4-byte boundary
+#pragma warning(suppress: 4146)
+    offset += (-(base + offset) & 3);
+
+    if (offset > length) {
+      return nullptr;
+    }
+
+    auto result = reinterpret_cast<VS_FIXEDFILEINFO*>(base + offset);
+    if (result->dwSignature != 0xFEEF04BD) {
+      return nullptr;
+    }
+
+    return result;
+  }
+
+private:
+  PIMAGE_DOS_HEADER mMzHeader;
+  PIMAGE_NT_HEADERS mPeHeader;
+  void*             mImageLimit;
+};
+
+inline HANDLE
+RtlGetProcessHeap()
+{
+  PTEB teb = ::NtCurrentTeb();
+  PPEB peb = teb->ProcessEnvironmentBlock;
+  return peb->Reserved4[1];
+}
+
+} // namespace nt
+} // namespace mozilla
+
+#endif // mozilla_NativeNt_h
--- a/browser/app/winlauncher/moz.build
+++ b/browser/app/winlauncher/moz.build
@@ -14,9 +14,13 @@ UNIFIED_SOURCES += [
 ]
 
 OS_LIBS += [
     'ntdll',
     'oleaut32',
     'ole32',
 ]
 
+TEST_DIRS += [
+    'test',
+]
+
 DisableStlWrapping()
new file mode 100644
--- /dev/null
+++ b/browser/app/winlauncher/test/TestNativeNt.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "NativeNt.h"
+
+#include <stdio.h>
+
+const wchar_t kNormal[] = L"Foo.dll";
+const wchar_t kHex12[] = L"Foo.ABCDEF012345.dll";
+const wchar_t kHex15[] = L"ABCDEF012345678.dll";
+const wchar_t kHex16[] = L"ABCDEF0123456789.dll";
+const wchar_t kHex17[] = L"ABCDEF0123456789a.dll";
+const wchar_t kHex24[] = L"ABCDEF0123456789cdabef98.dll";
+const wchar_t kHex8[] = L"01234567.dll";
+const wchar_t kNonHex12[] = L"Foo.ABCDEFG12345.dll";
+const wchar_t kHex13[] = L"Foo.ABCDEF0123456.dll";
+const wchar_t kHex11[] = L"Foo.ABCDEF01234.dll";
+const wchar_t kPrefixedHex16[] = L"Pabcdef0123456789.dll";
+
+const char kFailFmt[] = "TEST-FAILED | NativeNt | %s(%s) should have returned %s but did not\n";
+
+#define RUN_TEST(fn, varName, expected) \
+  if (fn(varName) == !expected) { \
+    printf(kFailFmt, #fn, #varName, #expected); \
+    return 1; \
+  }
+
+#define EXPECT_FAIL(fn, varName) \
+  RUN_TEST(fn, varName, false) \
+
+#define EXPECT_SUCCESS(fn, varName) \
+  RUN_TEST(fn, varName, true)
+
+using namespace mozilla::nt;
+
+int main(int argc, char* argv[])
+{
+  UNICODE_STRING normal;
+  ::RtlInitUnicodeString(&normal, kNormal);
+
+  UNICODE_STRING hex12;
+  ::RtlInitUnicodeString(&hex12, kHex12);
+
+  UNICODE_STRING hex16;
+  ::RtlInitUnicodeString(&hex16, kHex16);
+
+  UNICODE_STRING hex24;
+  ::RtlInitUnicodeString(&hex24, kHex24);
+
+  UNICODE_STRING hex8;
+  ::RtlInitUnicodeString(&hex8, kHex8);
+
+  UNICODE_STRING nonHex12;
+  ::RtlInitUnicodeString(&nonHex12, kNonHex12);
+
+  UNICODE_STRING hex13;
+  ::RtlInitUnicodeString(&hex13, kHex13);
+
+  UNICODE_STRING hex11;
+  ::RtlInitUnicodeString(&hex11, kHex11);
+
+  UNICODE_STRING hex15;
+  ::RtlInitUnicodeString(&hex15, kHex15);
+
+  UNICODE_STRING hex17;
+  ::RtlInitUnicodeString(&hex17, kHex17);
+
+  UNICODE_STRING prefixedHex16;
+  ::RtlInitUnicodeString(&prefixedHex16, kPrefixedHex16);
+
+  EXPECT_FAIL(Contains12DigitHexString, normal);
+  EXPECT_SUCCESS(Contains12DigitHexString, hex12);
+  EXPECT_FAIL(Contains12DigitHexString, hex13);
+  EXPECT_FAIL(Contains12DigitHexString, hex11);
+  EXPECT_FAIL(Contains12DigitHexString, hex16);
+  EXPECT_FAIL(Contains12DigitHexString, nonHex12);
+
+  EXPECT_FAIL(IsFileNameAtLeast16HexDigits, normal);
+  EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex12);
+  EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex24);
+  EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex16);
+  EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex17);
+  EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex8);
+  EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex15);
+  EXPECT_FAIL(IsFileNameAtLeast16HexDigits, prefixedHex16);
+
+  if (RtlGetProcessHeap() != ::GetProcessHeap()) {
+    printf("TEST-FAILED | NativeNt | RtlGetProcessHeap() is broken\n");
+    return 1;
+  }
+
+  return 0;
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/app/winlauncher/test/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DisableStlWrapping()
+
+CppUnitTests(['TestNativeNt'])
+
+LOCAL_INCLUDES += [
+    '/browser/app/winlauncher',
+]
+
+OS_LIBS += [
+    'ntdll',
+]