aosp12/sdk/find_lock/find_lock.cpp

779 lines
27 KiB
C++
Executable File

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* "find_lock.exe", for Windows only.
*
* References used:
*
* http://drdobbs.com/windows/184411099
* article by Sven B. Schreiber, November 01, 1999
*
* http://www.codeguru.com/Cpp/W-P/system/processesmodules/article.php/c2827/
* by Zoltan Csizmadia, November 14, 2000
*
* http://stackoverflow.com/questions/860656/
* (same technique, but written in unsafe C#)
*
* Starting with Vista, we can also use the Restart Manager API as
* explained here: (TODO for next version)
* http://msdn.microsoft.com/en-us/magazine/cc163450.aspx
*/
#ifdef _WIN32
#include "utils.h"
#include <ctype.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>
// NtDll structures from the the Dr Dobbs article, adjusted for our needs:
typedef void *POBJECT;
typedef LONG KPRIORITY;
typedef LARGE_INTEGER QWORD;
typedef struct {
WORD Length;
WORD MaximumLength;
PWORD Buffer;
} UNICODE_STRING;
typedef struct {
DWORD dIdProcess;
BYTE bObjectType; // OB_TYPE_*
BYTE bFlags; // bits 0..2 HANDLE_FLAG_*
WORD wValue; // multiple of 4
POBJECT pObject;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE;
typedef struct {
DWORD dCount;
SYSTEM_HANDLE ash[1];
} SYSTEM_HANDLE_INFORMATION;
typedef struct {
DWORD PeakVirtualSize;
DWORD VirtualSize;
DWORD PageFaultCount;
DWORD PeakWorkingSetSize;
DWORD WorkingSetSize;
DWORD QuotaPeakPagedPoolUsage;
DWORD QuotaPagedPoolUsage;
DWORD QuotaPeakNonPagedPoolUsage;
DWORD QuotaNonPagedPoolUsage;
DWORD PagefileUsage;
DWORD PeakPagefileUsage;
} VM_COUNTERS;
typedef struct {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;
typedef enum {
// Ignored. We don't actually use these values.
Unused
} KWAIT_REASON;
typedef struct {
QWORD qKernelTime; // 100 nsec units
QWORD qUserTime; // 100 nsec units
QWORD qCreateTime; // relative to 01-01-1601
DWORD d18;
PVOID pStartAddress;
CLIENT_ID Cid; // process/thread ids
DWORD dPriority;
DWORD dBasePriority;
DWORD dContextSwitches;
DWORD dThreadState; // 2=running, 5=waiting
KWAIT_REASON WaitReason;
DWORD dReserved01;
} SYSTEM_THREAD;
typedef struct {
DWORD dNext; // relative offset
DWORD dThreadCount;
DWORD dReserved01;
DWORD dReserved02;
DWORD dReserved03;
DWORD dReserved04;
DWORD dReserved05;
DWORD dReserved06;
QWORD qCreateTime; // relative to 01-01-1601
QWORD qUserTime; // 100 nsec units
QWORD qKernelTime; // 100 nsec units
UNICODE_STRING usName;
KPRIORITY BasePriority;
DWORD dUniqueProcessId;
DWORD dInheritedFromUniqueProcessId;
DWORD dHandleCount;
DWORD dReserved07;
DWORD dReserved08;
VM_COUNTERS VmCounters;
DWORD dCommitCharge; // bytes
SYSTEM_THREAD ast[1];
} SYSTEM_PROCESS_INFORMATION;
// The sic opcode for NtQuerySystemInformation
typedef enum {
SystemProcessInformation = 5,
SystemHandleInformation = 16,
} SYSTEMINFOCLASS;
#define STATUS_SUCCESS 0x00000000
#define STATUS_UNSUCCESSFUL 0xC0000001
#define STATUS_NOT_IMPLEMENTED 0xC0000002
#define STATUS_INVALID_INFO_CLASS 0xC0000003
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
#define STATUS_INVALID_PARAMETER 0xC000000D
typedef DWORD (WINAPI *NtQuerySystemInformationFuncPtr)(
DWORD sic, VOID* pData, DWORD sSize, ULONG* pdSize);
typedef DWORD (WINAPI *NtQueryInformationFileFuncPtr)(HANDLE, PVOID, PVOID, DWORD, DWORD);
typedef DWORD (WINAPI *NtQueryObjectFuncPtr)(HANDLE, DWORD, VOID*, DWORD, VOID*);
static NtQuerySystemInformationFuncPtr sNtQuerySystemInformationFunc;
static NtQueryInformationFileFuncPtr sNtQueryInformationFileFunc;
static NtQueryObjectFuncPtr sNtQueryObjectFunc;
//------------
// Get the NT DLL functions we need to use.
static bool init() {
sNtQuerySystemInformationFunc =
(NtQuerySystemInformationFuncPtr) GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
sNtQueryInformationFileFunc =
(NtQueryInformationFileFuncPtr) GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQueryInformationFile");
sNtQueryObjectFunc =
(NtQueryObjectFuncPtr) GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQueryObject");
return sNtQuerySystemInformationFunc != NULL &&
sNtQueryInformationFileFunc != NULL &&
sNtQueryObjectFunc != NULL;
}
static void terminate() {
sNtQuerySystemInformationFunc = NULL;
sNtQueryInformationFileFunc = NULL;
sNtQueryObjectFunc = NULL;
}
static bool adjustPrivileges() {
char *error = NULL;
HANDLE tokenH;
// Open a process token that lets us adjust privileges
BOOL ok = OpenProcessToken(GetCurrentProcess(), // ProcessHandle
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, // DesiredAccess
&tokenH); // TokenHandle
if (!ok) {
error = "OpenProcessToken failed: ";
goto bail_out;
}
// Lookup the privilege by name and get its local LUID token.
// What we request:
// SE_DEBUG_NAME, aka "SeDebugPrivilege"
// MSDN: Required to debug and adjust the memory of a process owned by another account.
// User Right: Debug programs.
TOKEN_PRIVILEGES priv;
priv.PrivilegeCount = 1;
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
ok = LookupPrivilegeValueA(NULL, // lpSystemName
SE_DEBUG_NAME, // lpName
&(priv.Privileges[0].Luid)); // lpLuid
if (!ok) {
error = "LookupPrivilegeValue failed: ";
goto bail_out;
}
ok = AdjustTokenPrivileges(tokenH, // TokenHandle
FALSE, // DisableAllPrivileges
&priv, // NewState
0, // BufferLength
NULL, // PreviousState
0); // ReturnLength
if (!ok) {
error = "AdjustTokenPrivileges failed: ";
goto bail_out;
}
bail_out:
if (error != NULL && gIsDebug) {
CString err;
err.setLastWin32Error(error);
fprintf(stderr, "%s", err.cstr());
}
if (tokenH != NULL) {
CloseHandle(tokenH);
}
return !!ok;
}
static bool getHandleType(HANDLE h, CString *type) {
bool result = false;
ULONG size = 0;
// Get the size of the type string
int status = sNtQueryObjectFunc(h, 2, NULL, 0, &size);
if (status == STATUS_INFO_LENGTH_MISMATCH && size > 0) {
// Get the type string itself
char *buf = new char[size];
status = sNtQueryObjectFunc(h, 2, buf, size, NULL);
if (status == 0 && size > 96) {
// The type string we want is a wide unicode (UTF16)
// zero-terminated string located at offset 96 in the
// buffer. In our case we want the string to be
// "Directory" or "File" so we know the max useful length
// is 9.
// Since we can only deal with ansi strings in this program,
// we'll make a crude copy of every other byte and just check
// that the other bytes are zero.
const char *c = buf + 96;
const char *e = buf + 96 + size;
// we'll write at the beginning of our buffer
char *dest = buf;
char *dend = dest + 9;
for (; c < e && dest < dend && c[0] != '\0' && c[1] == '\0'; c += 2, dest++) {
*dest = *c;
}
*(dest++) = '\0';
type->set(buf, dest - buf);
result = true;
}
free(buf);
}
return result;
}
// These is the wide unicode representations of the type we want to find.
static const char kFileW[] = "File";
static char isFileHandleType(HANDLE handle) {
char type = 0;
ULONG size = 0;
// Get the size of the type string
int status = sNtQueryObjectFunc(handle, 2, NULL, 0, &size);
if (status == STATUS_INFO_LENGTH_MISMATCH && size > 0) {
// Get the type string itself
char *buf = new char[size];
status = sNtQueryObjectFunc(handle, 2, buf, size, NULL);
if (status == 0 && size > 96) {
// The type string we want is a wide unicode (UTF16-LE)
// zero-terminated string located at offset 96 in the
// buffer. In our case we want the string to be "File".
//
// Since we're reading wide unicode, we want each character
// to be the one from our string followed by a zero byte.
// e.g. c should point to F \0 i \0 l \0 e \0 \0 \0.
const char *c = buf + 96;
type = c[0];
int len = sizeof(kFileW);
const char *d = kFileW;
for (; type != 0 && len > 0; c+=2, d++, len--) {
if (c[0] != *d || c[1] != 0) {
type = 0;
break;
}
}
}
free(buf);
}
return type;
}
typedef struct {
HANDLE handle;
CString *outStr;
bool result;
} SFileNameInfo;
static unsigned __stdcall FileNameThreadFunc(void *param) {
SFileNameInfo *info = (SFileNameInfo *)param;
if (info == NULL) {
return 1;
}
char buf[MAX_PATH*2 + 4];
DWORD iob[2] = { 0, 0 };
DWORD status = sNtQueryInformationFileFunc(info->handle, iob, buf, sizeof(buf), 9);
if (status == STATUS_SUCCESS) {
// The result is a buffer with:
// - DWORD (4 bytes) for the *byte* length (so twice the character length)
// - Actual string in Unicode
// Not sure of the actual type, but it does look like a UNICODE_STRING struct.
DWORD len = ((DWORD *)buf)[0];
if (len <= MAX_PATH * 2) {
// We can't handle wide Unicode. What we do is convert it into
// straight ansi by just retaining the first of each couple bytes.
// Bytes that cannot be mapped (e.g. 2nd byte is != 0) will be
// simply converted to 0xFF.
unsigned char *dest = (unsigned char *)buf + 4;
unsigned char *src = (unsigned char *)buf + 4;
for (DWORD i = 0; i < len; dest++, src += 2, i += 2) {
if (src[1] == 0) {
*dest = *src;
} else {
*dest = 0xFF;
}
}
*dest = '\0';
info->outStr->set(buf + 4, len);
info->result = true;
return 0;
}
}
return 1;
}
static bool getFileName(HANDLE handle, CString *outStr) {
SFileNameInfo info;
info.handle = handle;
info.outStr = outStr;
info.result = false;
// sNtQueryInformationFileFunc might hang on some handles.
// A trick is to do it in a thread and if it takes too loog then
// just shutdown the thread, since it's deadlocked anyway.
unsigned threadId;
HANDLE th = (HANDLE)_beginthreadex(NULL, // security
0, // stack_size
&FileNameThreadFunc, // address
&info, // arglist
0, // initflag
&threadId); // thrdaddr
if (th == NULL) {
// Failed to create thread. Shouldn't really happen.
outStr->set("<failed to create thread>");
return false;
}
bool result = false;
// Wait for thread or kill it if it takes too long.
if (WaitForSingleObject(th /*handle*/, 200 /*ms*/) == WAIT_TIMEOUT) {
TerminateThread(th /*handle*/, 0 /*retCode*/);
outStr->set("<timeout>");
} else {
result = info.result;
}
CloseHandle(th);
return result;
}
// Find the name of the process (e.g. "java.exe") given its id.
// processesPtr must be the list returned by getAllProcesses().
// Special handling for javaw.exe: this isn't quite useful so
// we also try to find and append the parent process name.
static bool getProcessName(SYSTEM_PROCESS_INFORMATION *processesPtr,
DWORD remoteProcessId,
CString *outStr) {
SYSTEM_PROCESS_INFORMATION *ptr = processesPtr;
while (ptr != NULL) {
if (ptr->dUniqueProcessId == remoteProcessId) {
// This is the process we want.
UNICODE_STRING *uniStr = &(ptr->usName);
WORD len = uniStr->Length;
char buf[MAX_PATH];
if (len <= MAX_PATH * 2) {
// We can't handle wide Unicode. What we do is convert it into
// straight ansi by just retaining the first of each couple bytes.
// Bytes that cannot be mapped (e.g. 2nd byte is != 0) will be
// simply converted to 0xFF.
unsigned char *dest = (unsigned char *)buf;
unsigned char *src = (unsigned char *)uniStr->Buffer;
for (WORD i = 0; i < len; dest++, src += 2, i += 2) {
if (src[1] == 0) {
*dest = *src;
} else {
*dest = 0xFF;
}
}
*dest = '\0';
outStr->set(buf, len);
if (strcmp(buf, "javaw.exe") == 0) {
// Heuristic: eclipse often shows up as javaw.exe
// but what is useful is to report eclipse to the user
// instead.
// So in this case, look at the parent and report it too.
DWORD parentId = ptr->dInheritedFromUniqueProcessId;
if (parentId > 0) {
CString name2;
bool ok2 = getProcessName(processesPtr,
parentId,
&name2);
if (ok2) {
outStr->add(" (");
outStr->add(name2.cstr());
outStr->add(")");
}
}
}
return true;
}
}
// Look at the next process, if any.
if (ptr->dNext == NULL) {
break;
} else {
ptr = (SYSTEM_PROCESS_INFORMATION *)((char *)ptr + ptr->dNext);
}
}
outStr->setf("<process id %08x name not found>", remoteProcessId);
return false;
}
// Query system for all processes information.
// Returns an error string in case of error.
// Returns the virtual_alloc-allocated buffer on success or NULL on error.
// It's up to the caller to do a VirtualFree on the returned buffer.
static SYSTEM_PROCESS_INFORMATION *queryAllProcess(const char **error) {
// Allocate a buffer for the process information. We don't know the
// exact size. A normal system might typically have between 100-200 processes.
// We'll resize the buffer if not big enough.
DWORD infoSize = 4096;
SYSTEM_PROCESS_INFORMATION *infoPtr =
(SYSTEM_PROCESS_INFORMATION *) VirtualAlloc(NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
if (infoPtr != NULL) {
// Query the actual size needed (or the data if it fits in the buffer)
DWORD needed = 0;
if (sNtQuerySystemInformationFunc(
SystemProcessInformation, infoPtr, infoSize, &needed) != 0) {
if (needed == 0) {
// Shouldn't happen.
*error = "No processes found";
goto bail_out;
}
// Realloc
VirtualFree(infoPtr, 0, MEM_RELEASE);
infoSize += needed;
infoPtr = (SYSTEM_PROCESS_INFORMATION *) VirtualAlloc(
NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
// Query all the processes objects again
if (sNtQuerySystemInformationFunc(
SystemProcessInformation, infoPtr, infoSize, NULL) != 0) {
*error = "Failed to query system processes";
goto bail_out;
}
}
}
if (infoPtr == NULL) {
*error = "Failed to allocate system processes info buffer";
goto bail_out;
}
bail_out:
if (*error != NULL) {
VirtualFree(infoPtr, 0, MEM_RELEASE);
infoPtr = NULL;
}
return infoPtr;
}
// Query system for all handle information.
// Returns an error string in case of error.
// Returns the virtual_alloc-allocated buffer on success or NULL on error.
// It's up to the caller to do a VirtualFree on the returned buffer.
static SYSTEM_HANDLE_INFORMATION *queryAllHandles(const char **error) {
// Allocate a buffer. It won't be large enough to get the handles
// (e.g. there might be 10k or 40k handles around). We'll resize
// it once we know the actual size.
DWORD infoSize = 4096;
SYSTEM_HANDLE_INFORMATION *infoPtr =
(SYSTEM_HANDLE_INFORMATION *) VirtualAlloc(NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
if (infoPtr != NULL) {
// Query the actual size needed
DWORD needed = 0;
if (sNtQuerySystemInformationFunc(
SystemHandleInformation, infoPtr, infoSize, &needed) != 0) {
if (needed == 0) {
// Shouldn't happen.
*error = "No handles found";
goto bail_out;
}
// Realloc
VirtualFree(infoPtr, 0, MEM_RELEASE);
infoSize += needed;
infoPtr = (SYSTEM_HANDLE_INFORMATION *) VirtualAlloc(
NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
}
}
if (infoPtr == NULL) {
*error = "Failed to allocate system handle info buffer";
goto bail_out;
}
// Query all the handle objects
if (sNtQuerySystemInformationFunc(SystemHandleInformation, infoPtr, infoSize, NULL) != 0) {
*error = "Failed to query system handles";
goto bail_out;
}
bail_out:
if (*error != NULL) {
VirtualFree(infoPtr, 0, MEM_RELEASE);
infoPtr = NULL;
}
return infoPtr;
}
bool findLock(CPath &path, CString *outModule) {
bool result = false;
const char *error = NULL;
SYSTEM_PROCESS_INFORMATION *processesPtr = NULL;
SYSTEM_HANDLE_INFORMATION *handlesPtr = NULL;
const HANDLE currProcessH = GetCurrentProcess();
const DWORD currProcessId = GetCurrentProcessId();
HANDLE remoteProcessH = NULL;
DWORD remoteProcessId = 0;
DWORD matchProcessId = 0;
int numHandleFound = 0;
int numHandleChecked = 0;
int numHandleDirs = 0;
int numHandleFiles = 0;
int numProcessMatch = 0;
BYTE ob_type_file = 0;
// Get the path to search, without the drive letter.
const char *searchPath = path.cstr();
if (isalpha(searchPath[0]) && searchPath[1] == ':') {
searchPath += 2;
}
size_t searchPathLen = strlen(searchPath);
if (gIsDebug) fprintf(stderr, "Search path: '%s'\n", searchPath);
if (!init()) {
error = "Failed to bind to ntdll.dll";
goto bail_out;
}
if (!adjustPrivileges()) {
// We can still continue even if the privilege escalation failed.
// The apparent effect is that we'll fail to query the name of
// some processes, yet it will work for some of them.
if (gIsDebug) fprintf(stderr, "Warning: adusting privileges failed. Continuing anyway.\n");
} else {
if (gIsDebug) fprintf(stderr, "Privileges adjusted.\n"); // DEBUG remove lter
}
processesPtr = queryAllProcess(&error);
if (processesPtr == NULL) goto bail_out;
handlesPtr = queryAllHandles(&error);
if (handlesPtr == NULL) goto bail_out;
numHandleFound = handlesPtr->dCount;
// Check all the handles
for (int n = handlesPtr->dCount, i = 0; i < n; i++) {
SYSTEM_HANDLE sysh = handlesPtr->ash[i];
if (ob_type_file != 0 && sysh.bObjectType != ob_type_file) {
continue;
}
HANDLE handle = (HANDLE) sysh.wValue;
DWORD remoteId = sysh.dIdProcess;
HANDLE remoteH = NULL;
if (remoteId == matchProcessId) {
// We already matched that process, we can skip its other entries.
continue;
}
if (remoteId == currProcessId) {
// We don't match ourselves
continue;
}
// Open a remote process.
// Most entries of a given process seem to be consecutive, so we
// only open the remote process handle if it's a different id.
if (remoteProcessH == NULL && remoteId == remoteProcessId) {
// We already tried to open this process and it failed.
// It's not going to be any better the next time so skip it.
continue;
}
if (remoteProcessH == NULL || remoteId != remoteProcessId) {
if (remoteProcessH != NULL) {
CloseHandle(remoteProcessH);
}
remoteProcessId = remoteId;
remoteProcessH = OpenProcess(PROCESS_DUP_HANDLE,
FALSE /*inheritHandle*/,
remoteProcessId);
if (remoteProcessH == NULL) {
continue;
}
}
if (remoteProcessH != NULL) {
// Duplicate the remote handle
if (DuplicateHandle(remoteProcessH, // hSourceProcessHandle
handle, // hSourceHandle
currProcessH, // hTargetProcessHandle
&remoteH, // lpTargetHandle
0, // dwDesiredAccess (ignored by same access)
FALSE, // bInheritHandle
DUPLICATE_SAME_ACCESS) == 0) {
continue;
}
}
numHandleChecked++;
char type = isFileHandleType(remoteH);
if (type != 0) {
if (type == 'D') numHandleDirs++;
else if (type == 'F') numHandleFiles++;
// TODO simplify by not keeping directory handles
if (ob_type_file == 0 && type == 'F') {
// We found the first file handle. Remember it's system_handle object type
// and then use it to filter the following system_handle.
// For some reason OB_TYPE_FILE should be 0x1A but empirically I find it
// to be 0x1C, so we just make this test more dynamic.
ob_type_file = sysh.bObjectType;
}
// Try to get a filename out of that file or directory handle.
CString name("<unknown>");
bool ok = getFileName(remoteH, &name);
if (gIsDebug) {
fprintf(stderr, "P:%08x | t:%02x | f:%02x | v:%08x | %c | %s %s\n",
sysh.dIdProcess, sysh.bObjectType, sysh.bFlags, sysh.wValue,
type,
ok ? "OK" : "FAIL",
name.cstr()
);
}
if (ok) {
// We got a file path. Let's check if it matches our target path.
if (_strnicmp(searchPath, name.cstr(), searchPathLen) == 0) {
// Remember this process id so that we can ignore all its following entries.
matchProcessId = remoteId;
// Find out its process name
CString procName("<unknown>");
ok = getProcessName(processesPtr, remoteProcessId, &procName);
if (ok) {
numProcessMatch++;
if (!outModule->isEmpty()) {
outModule->add(";");
}
outModule->add(procName.cstr());
result = true;
}
if (gIsDebug) {
fprintf(stderr, "==> MATCH FOUND: %s %s\n",
ok ? "OK" : "FAIL",
procName.cstr()
);
}
}
}
}
if (remoteH != NULL) {
CloseHandle(remoteH);
remoteH = NULL;
}
}
bail_out:
if (gIsDebug) {
fprintf(stderr, "Processes matched: %d\n", numProcessMatch);
fprintf(stderr, "Handles: %d found, %d checked, %d dirs, %d files\n",
numHandleFound,
numHandleChecked,
numHandleDirs,
numHandleFiles);
}
if (error != NULL) {
CString msg;
msg.setLastWin32Error(NULL);
if (gIsDebug) fprintf(stderr, "[ERROR] %s: %s", error, msg.cstr());
}
if (remoteProcessH != NULL) {
CloseHandle(remoteProcessH);
}
if (currProcessH != NULL) {
CloseHandle(currProcessH);
}
if (handlesPtr != NULL) {
VirtualFree(handlesPtr, 0, MEM_RELEASE);
handlesPtr = NULL;
}
terminate();
return result;
}
#endif /* _WIN32 */