changeset 2601:648dc28c87ed

[PATCH] read process environment on Windows 10.0 This patch adds the ability to read another process' environment strings on Windows 10.0 It uses a sequence of undocumented structures and calls to do this, and will not work on 32-bit processes, for example. It is likely but not guaranteed to work in future versions of Windows, and I cannot test it for Windows 8 compatiblity. This patch also changes the code to read the Windows version and build number. The previous code apparently no longer returns the actual host information, but as of Windows 8.1 returns the build machine's information. Unfortunately the new code can not return the build number. reviewed-by: ebaron review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-February/022232.html
author Simon Tooke <stooke@redhat.com>
date Tue, 28 Feb 2017 14:33:51 -0500
parents fb44fdd2a164
children 4d7312c27c6c
files common/portability/Makefile common/portability/src/main/java/com/redhat/thermostat/common/portability/PortableHostImpl.java common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/macos/MacOSHostImpl.java common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/windows/WindowsHelperImpl.java common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/windows/WindowsPortableProcessImpl.java common/portability/src/main/native/WindowsHelperImpl.c common/portability/src/test/java/com/redhat/thermostat/common/portability/internal/windows/WindowsHelperImplTest.java
diffstat 7 files changed, 530 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- a/common/portability/Makefile	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/Makefile	Tue Feb 28 14:33:51 2017 -0500
@@ -61,7 +61,7 @@
 ifeq ($(JNI_PLATFORM),win32)
     EXECUTABLES  += $(HELPER_EXECUTABLE)
     JNI_LIST     +=  com.redhat.thermostat.common.portability.internal.windows.WindowsHelperImpl
-    HELPER_LIBS  += -l psapi
+    HELPER_LIBS  += -l psapi -l Netapi32
 endif
 
 ifeq ($(JNI_PLATFORM),darwin)
--- a/common/portability/src/main/java/com/redhat/thermostat/common/portability/PortableHostImpl.java	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/src/main/java/com/redhat/thermostat/common/portability/PortableHostImpl.java	Tue Feb 28 14:33:51 2017 -0500
@@ -47,7 +47,7 @@
 
     private static PortableHost createInstance() {
         return OS.IS_LINUX ? LinuxPortableHostImpl.createInstance()
-                : OS.IS_WINDOWS ? WindowsPortableHostImpl.createInstance() : MacOSHostImpl.INSTANCE;
+                : OS.IS_WINDOWS ? WindowsPortableHostImpl.createInstance() : MacOSHostImpl.createInstance();
     }
 
     public static PortableHost getInstance() {
--- a/common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/macos/MacOSHostImpl.java	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/macos/MacOSHostImpl.java	Tue Feb 28 14:33:51 2017 -0500
@@ -41,9 +41,12 @@
 
 public class MacOSHostImpl implements PortableHost {
 
-    public static final MacOSHostImpl INSTANCE = new MacOSHostImpl();
     private static final MacOSHelperImpl helper = MacOSHelperImpl.INSTANCE;
 
+    public static PortableHost createInstance() {
+        return new MacOSHostImpl();
+    }
+
     @Override
     public String getHostName() {
         return helper.getHostName();
--- a/common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/windows/WindowsHelperImpl.java	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/windows/WindowsHelperImpl.java	Tue Feb 28 14:33:51 2017 -0500
@@ -40,6 +40,7 @@
 import com.redhat.thermostat.shared.config.NativeLibraryResolver;
 import com.redhat.thermostat.shared.config.OS;
 
+import java.nio.ByteBuffer;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -112,7 +113,8 @@
     public String getOSVersion() {
         final long info[] = new long[3];  // major, minor, build
         getOSVersion0(info);
-        return "" + info[0] + "" + info[1] + " (Build " + info[2] + ")";
+        // the build number is not available on newer windows versions (8.1 and up)
+        return (info[2] != 0) ? "" + info[0] + "" + info[1] + " (Build " + info[2] + ")" : "" + info[0] + "." + info[1];
     }
 
     public String getCPUModel() {
@@ -154,6 +156,10 @@
 
     // local process-specific information
 
+    public int getCurrentProcessPid() {
+        return getCurrentProcessID0();
+    }
+
     public boolean exists(int pid) {
         final long hnd = getLimitedProcessHandle0(pid);
         if (hnd != 0) {
@@ -176,22 +182,74 @@
         return Integer.parseInt(uidStr);
     }
 
+    private String extractString( ByteBuffer buff, char terminator) {
+        StringBuilder sb = new StringBuilder();
+        while (buff.hasRemaining()) {
+            char c = buff.getChar();
+            if (c == terminator || c == 0) {
+                break;
+            }
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
     public Map<String, String> getEnvironment(int pid) {
-        // the environment is returned as a 1D array of alternating env names and values
-        final String[] envArray = getEnvironment0(pid);
-        if (envArray == null || envArray.length == 0) {
+        final long hProcess = pid != 0 ? getProcessHandle0(pid) : getCurrentProcessHandle0();
+        final Object obj = getEnvironment0(hProcess, 0);
+        closeHandle0(hProcess);
+        if (obj == null) {
             return Collections.emptyMap();
         }
-
-        if (envArray.length % 2 != 0) {
-            throw new AssertionError("environment array length not even");
+        else if (obj instanceof String) {
+            System.err.println("env 0 returns " + obj);
         }
+        else /* if (obj instanceof ByteBuffer) */ {
+            final ByteBuffer buff = (ByteBuffer)(obj);
+            final Map<String, String> env = new HashMap<>();
+            buff.get();
+            while (buff.hasRemaining()) {
+                String k = extractString(buff, '=');
+                if (k.isEmpty()) {
+                    break;
+                }
+                String v = extractString(buff, '\0');
+                env.put(k, v);
+            }
+            freeDirectBuffer0(buff);
+            return env;
+        }
+        return null;
+    }
 
-        final Map<String, String> env = new HashMap<>(envArray.length/2);
-        for (int i = 0; i < envArray.length / 2; i++) {
-            env.put(envArray[i * 2], envArray[i * 2 + 1]);
+    public String getCWD(int pid) {
+        final long hProcess = pid != 0 ? getProcessHandle0(pid) : getCurrentProcessHandle0();
+        final Object obj = getEnvironment0(hProcess, 1);
+        closeHandle0(hProcess);
+        if (obj == null) {
+            return null;
         }
-        return env;
+        return (String)(obj);
+    }
+
+    public String getExecutable(int pid) {
+        final long hProcess = pid != 0 ? getProcessHandle0(pid) : getCurrentProcessHandle0();
+        final Object obj = getEnvironment0(hProcess, 2);
+        closeHandle0(hProcess);
+        if (obj == null) {
+            return null;
+        }
+        return (String)(obj);
+    }
+
+    public String getCommandLine(int pid) {
+        final long hProcess = pid != 0 ? getProcessHandle0(pid) : getCurrentProcessHandle0();
+        final Object obj = getEnvironment0(hProcess, 3);
+        closeHandle0(hProcess);
+        if (obj == null) {
+            return null;
+        }
+        return (String)(obj);
     }
 
     /**
@@ -246,13 +304,15 @@
 
     private static native String getProcessSID0(int pid);
     private static native String getUserName0(int pid, boolean prependDomain);
-    private static native String[] getEnvironment0(int pid);
+    private static native Object getEnvironment0(long hProcess, int mode); // mode = 0 returns DirectByteBuffer, 1 = String cwd, 2 = String execuatable, 3 = String command line
     private static native boolean getProcessInfo0(int pid, long[] info);
     private static native boolean getProcessIOInfo0(int pid, long[] info);
 
+    private static native int getCurrentProcessID0();
     private static native long getCurrentProcessHandle0();
     private static native long getProcessHandle0(int pid);
     private static native long getLimitedProcessHandle0(int pid);
     private static native void closeHandle0(long handle);
+    private static native void freeDirectBuffer0(final ByteBuffer byteBuffer);
     private static native boolean terminateProcess0(int pid, int exitCode, int waitMillis);
 }
--- a/common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/windows/WindowsPortableProcessImpl.java	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/src/main/java/com/redhat/thermostat/common/portability/internal/windows/WindowsPortableProcessImpl.java	Tue Feb 28 14:33:51 2017 -0500
@@ -41,6 +41,7 @@
 import com.redhat.thermostat.common.portability.PortableProcessStat;
 import com.redhat.thermostat.common.portability.PortableVmIoStat;
 
+import java.util.Collections;
 import java.util.Map;
 
 public class WindowsPortableProcessImpl implements PortableProcess {
@@ -69,7 +70,8 @@
 
     @Override
     public Map<String, String> getEnvironment(int pid) {
-        return helper.getEnvironment(pid);
+        final Map<String, String> envMap = helper.getEnvironment(pid);
+        return envMap != null ? envMap : Collections.<String, String>emptyMap();
     }
 
     @Override
--- a/common/portability/src/main/native/WindowsHelperImpl.c	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/src/main/native/WindowsHelperImpl.c	Tue Feb 28 14:33:51 2017 -0500
@@ -45,6 +45,13 @@
 # include <winsock2.h>
 # include <psapi.h>
 # include <intrin.h>
+# include <Sddl.h>
+# include <Lm.h>
+# include <Winternl.h>
+#endif
+
+#if !defined(STATUS_NOT_IMPLEMENTED)
+# define STATUS_NOT_IMPLEMENTED 0xC0000002;
 #endif
 
 #define MAX_NAME 256
@@ -52,6 +59,9 @@
 #define NI_MAXHOST 1025
 #endif /* NI_MAXHOST */
 
+// to debug the getEnvironment0() code, uncomment this
+//#define DEBUG_GETENV
+
 #if defined(DEBUG)
 static void testLength(JNIEnv* env, jlongArray array, int minLength) {
     // sanity test
@@ -69,8 +79,8 @@
  */
 JNIEXPORT jstring JNICALL
 Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getHostName0
-  (JNIEnv *env, jclass winHelperClass, jboolean prependDomain)
-{
+  (JNIEnv *env, jclass winHelperClass, jboolean prependDomain) {
+
       char hostname[NI_MAXHOST];
       memset(hostname, 0, sizeof(hostname));
 
@@ -86,20 +96,29 @@
  * Signature: ([J)V
  */
 JNIEXPORT void JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getOSVersion0
-  (JNIEnv *env, jclass winHelperClass, jlongArray array)
-{
+  (JNIEnv *env, jclass winHelperClass, jlongArray array) {
+
     testLength(env, array, 3);
 
     // Get the element pointer
     jlong* data = (*env)->GetLongArrayElements(env, array, 0);
 
+    // NOTE: after version 8.1, this function is deprecated, and may just return the compile OS
+    /***
     OSVERSIONINFOEX vinfo;
     vinfo.dwOSVersionInfoSize = sizeof(vinfo);
-    GetVersionEx(&vinfo);
+    GetVersionEx((LPOSVERSIONINFO)(&vinfo));
     data[0] = vinfo.dwMajorVersion;
     data[1] = vinfo.dwMinorVersion;
     data[2] = vinfo.dwBuildNumber;
+    ***/
 
+    LPBYTE bufPtr = 0;
+    NetWkstaGetInfo(NULL, 100, &bufPtr);
+    data[0] = ((WKSTA_INFO_100*)(bufPtr))->wki100_ver_major;
+    data[1] = ((WKSTA_INFO_100*)(bufPtr))->wki100_ver_minor;
+    data[2] = 0; /* unavailable */
+    NetApiBufferFree(bufPtr);
     (*env)->ReleaseLongArrayElements(env, array, data, 0);
 }
 
@@ -110,8 +129,7 @@
  * Signature: ([J)V
  */
 JNIEXPORT boolean JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getGlobalMemoryStatus0
-  (JNIEnv *env, jclass winHelperClass, jlongArray array)
-{
+  (JNIEnv *env, jclass winHelperClass, jlongArray array) {
     testLength(env, array, 8);
 
     // Get the element pointer
@@ -134,15 +152,13 @@
     return TRUE;
 }
 
-
 /*
  * Class:     com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl
  * Method:    getCPUString0
  * Signature: ()Ljava/lang/String;
  */
 JNIEXPORT jstring JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getCPUString0
-  (JNIEnv *env, jclass winHelperClass)
-{
+  (JNIEnv *env, jclass winHelperClass) {
     // Get extended ids.
     int CPUInfo[4] = {-1};
     __cpuid(CPUInfo, 0x80000000);
@@ -150,30 +166,19 @@
 
     // Get the information associated with each extended ID.
     char CPUBrandString[0x40] = { 0 };
-    for( unsigned int i=0x80000000; i<=nExIds; ++i)
-    {
+    for(unsigned int i = 0x80000000; i <= nExIds; ++i) {
         __cpuid(CPUInfo, i);
 
         // Interpret CPU brand string and cache information.
-        if  (i == 0x80000002)
-        {
-            memcpy( CPUBrandString,
-            CPUInfo,
-            sizeof(CPUInfo));
-        }
-        else if( i == 0x80000003 )
-        {
-            memcpy( CPUBrandString + 16,
-            CPUInfo,
-            sizeof(CPUInfo));
-        }
-        else if( i == 0x80000004 )
-        {
+        if  (i == 0x80000002) {
+            memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo));
+        } else if(i == 0x80000003) {
+            memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo));
+        } else if(i == 0x80000004) {
             memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo));
         }
     }
     return (*env)->NewStringUTF(env, CPUBrandString);
-
 }
 
 /*
@@ -182,8 +187,8 @@
  * Signature: ()I
  */
 JNIEXPORT jint JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getCPUCount0
-  (JNIEnv *env, jclass winHelperClass)
-{
+  (JNIEnv *env, jclass winHelperClass) {
+
     SYSTEM_INFO sysinfo;
     GetSystemInfo(&sysinfo);
 
@@ -196,8 +201,8 @@
  * Signature: ()I
  */
 JNIEXPORT jlong JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_queryPerformanceFrequency0
-  (JNIEnv *env, jclass winHelperClass)
-{
+  (JNIEnv *env, jclass winHelperClass) {
+
     LARGE_INTEGER freq;
     QueryPerformanceFrequency(&freq);
 
@@ -211,22 +216,20 @@
  */
 JNIEXPORT jstring JNICALL
 Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getProcessSID0
-  (JNIEnv *env, jclass winHelperClass, jint pid)
-{
+  (JNIEnv *env, jclass winHelperClass, jint pid) {
     HANDLE hProcess;
 
-    hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid );
+    hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
     if (NULL == hProcess)
         return NULL;
 
     HANDLE hToken = NULL;
 
-    if( !OpenProcessToken( hProcess, TOKEN_QUERY, &hToken ) ) {
+    if(!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
         CloseHandle( hProcess );
         return NULL;
     }
 
-    DWORD dwSize = MAX_NAME;
     DWORD dwLength = 0;
     PTOKEN_USER ptu = NULL;
 
@@ -246,8 +249,8 @@
         ptu = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);
 
         if (ptu == NULL) {
-            CloseHandle( hToken );
-            CloseHandle( hProcess );
+            CloseHandle(hToken);
+            CloseHandle(hProcess);
             return NULL;
         }
     }
@@ -262,13 +265,13 @@
         if (ptu != NULL) {
             HeapFree(GetProcessHeap(), 0, (LPVOID)ptu);
         }
-        CloseHandle( hToken );
-        CloseHandle( hProcess );
+        CloseHandle(hToken);
+        CloseHandle(hProcess);
         return NULL;
     }
 
     LPWSTR stringSid;
-    ConvertSidToStringSidW( ptu->User.Sid, &stringSid );
+    ConvertSidToStringSidW(ptu->User.Sid, &stringSid);
     jstring s = (*env)->NewString(env, (const jchar *)stringSid, (jsize)wcslen(stringSid));
 
     LocalFree(stringSid);
@@ -276,8 +279,8 @@
     if (ptu != NULL) {
         HeapFree(GetProcessHeap(), 0, (LPVOID)ptu);
     }
-    CloseHandle( hToken );
-    CloseHandle( hProcess );
+    CloseHandle(hToken);
+    CloseHandle(hProcess);
     return s;
 }
 
@@ -298,12 +301,11 @@
 
     HANDLE hToken = NULL;
 
-    if( !OpenProcessToken( hProcess, TOKEN_QUERY, &hToken ) ) {
-        CloseHandle( hProcess );
+    if(!OpenProcessToken( hProcess, TOKEN_QUERY, &hToken)) {
+        CloseHandle(hProcess);
         return NULL;
     }
 
-    DWORD dwSize = MAX_NAME;
     DWORD dwLength = 0;
     PTOKEN_USER ptu = NULL;
 
@@ -315,16 +317,16 @@
         &dwLength       // receives required buffer size
     )) {
         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
-            CloseHandle( hToken );
-            CloseHandle( hProcess );
+            CloseHandle(hToken);
+            CloseHandle(hProcess);
             return NULL;
         }
 
         ptu = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);
 
         if (ptu == NULL) {
-            CloseHandle( hToken );
-            CloseHandle( hProcess );
+            CloseHandle(hToken);
+            CloseHandle(hProcess);
             return NULL;
         }
     }
@@ -339,34 +341,30 @@
         if (ptu != NULL) {
             HeapFree(GetProcessHeap(), 0, (LPVOID)ptu);
         }
-        CloseHandle( hToken );
-        CloseHandle( hProcess );
+        CloseHandle(hToken);
+        CloseHandle(hProcess);
         return NULL;
     }
 
+    DWORD dwSize = MAX_NAME;
     SID_NAME_USE SidType;
     wchar_t lpName[MAX_NAME];
     wchar_t lpDomain[MAX_NAME*2 + 1];  // room for '\' + lpName
     jstring s = NULL;
 
-    if( !LookupAccountSidW( NULL , ptu->User.Sid, lpName, &dwSize, lpDomain, &dwSize, &SidType ) )
-    {
+    if( !LookupAccountSidW(NULL , ptu->User.Sid, lpName, &dwSize, lpDomain, &dwSize, &SidType)) {
         DWORD dwResult = GetLastError();
-        if( dwResult == ERROR_NONE_MAPPED )
-           strcpy (lpName, "NONE_MAPPED" );
-        else
-        {
-            printf("LookupAccountSid Error %u\n", GetLastError());
+        if(dwResult == ERROR_NONE_MAPPED) {
+            wcscpy(lpName, L"NONE_MAPPED");
+        } else {
+            fprintf(stderr, "LookupAccountSid Error %ld\n", GetLastError());
         }
-    }
-    else
-    {
+    } else {
         if (prependDomain) {
             wcscat(lpDomain, L"\\");
             wcscat(lpDomain, lpName);
             s = (*env)->NewString(env, (const jchar *)lpDomain, (jsize)wcslen(lpDomain));
-        }
-        else {
+        } else {
             s = (*env)->NewString(env, (const jchar *)lpName, (jsize)wcslen(lpName));
         }
     }
@@ -374,28 +372,295 @@
     if (ptu != NULL) {
         HeapFree(GetProcessHeap(), 0, (LPVOID)ptu);
     }
-    CloseHandle( hToken );
-    CloseHandle( hProcess );
+    CloseHandle(hToken);
+    CloseHandle(hProcess);
     return s;
 }
 
+/**
+
+    The getEnvironment0() code is a redo of some 32-bit code examples found all over the web - updated and turned into JNI
+
+    See
+
+    https://blogs.msdn.microsoft.com/matt_pietrek/2004/08/25/reading-another-processs-environment/
+    https://sites.google.com/site/x64lab/home/notes-on-x64-windows-gui-programming/exploring-peb-process-environment-block (blog MPL)
+    https://github.com/conix-security/zer0m0n/blob/master/src/driver/include/nt/structures/RTL_USER_PROCESS_PARAMETERS.h (GPL 3)
+    https://blog.gapotchenko.com/eazfuscator.net/reading-environment-variables
+    https://www.codeproject.com/Articles/25647/Read-Environment-Strings-of-Remote-Process
+    https://www.reactos.org/
+**/
+
+/**
+ * Wrapper to call NtQueryInformationProcess API by Run-Time Dynamic Linking
+ * Check MSDN Documentation : http://msdn2.microsoft.com/en-us/library/ms684280(VS.85).aspx
+ **/
+NTSTATUS QueryInformationProcesss(
+		IN HANDLE ProcessHandle,
+		IN PROCESSINFOCLASS ProcessInformationClass,
+		OUT PVOID ProcessInformation,
+		IN ULONG ProcessInformationLength,
+		OUT PULONG ReturnLength OPTIONAL
+		)
+{
+	typedef NTSTATUS ( __stdcall *QueryInfoProcess) (
+		IN HANDLE ProcessHandle,
+		IN PROCESSINFOCLASS ProcessInformationClass,
+		OUT PVOID ProcessInformation,
+		IN ULONG ProcessInformationLength,
+		OUT PULONG ReturnLength OPTIONAL
+		);
+
+	HMODULE hModNTDll = LoadLibrary("ntdll.dll");
+
+	if (!hModNTDll) {
+		fprintf(stderr, "Error Loading library\n");
+	}
+
+	QueryInfoProcess QueryProcInfo = (QueryInfoProcess) GetProcAddress(hModNTDll, "NtQueryInformationProcess");
+	if (!QueryProcInfo) {
+		fprintf(stderr, "Can't find NtQueryInformationProcess in ntdll.dll");
+		return STATUS_NOT_IMPLEMENTED;
+	}
+
+	NTSTATUS ntStat =  QueryProcInfo( ProcessHandle,
+		  							  ProcessInformationClass,
+									  ProcessInformation,
+									  ProcessInformationLength,
+									  ReturnLength );
+
+	FreeLibrary(hModNTDll);
+	return ntStat;
+}
+
+static BOOL checkReadAccess(HANDLE hProcess, void* pAddress, int* nSize) {
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "enter HasReadAccess(%ld, addr = 0x%lx\n", (long)hProcess, (long)pAddress);
+#endif //DEBUG_GETENV
+    MEMORY_BASIC_INFORMATION64 memInfo;
+    SIZE_T msize = VirtualQueryEx(hProcess, pAddress, (PMEMORY_BASIC_INFORMATION)(&memInfo), sizeof(memInfo));
+    if(msize == 0 || PAGE_NOACCESS == memInfo.Protect || PAGE_EXECUTE == memInfo.Protect) {
+        *nSize = 0;
+        fprintf(stderr, "Failed to query memory access, err=%ld prot=%ld NOACC=%ld  EX=%ld msize=%ld\n", (long)GetLastError(),
+             (long)memInfo.Protect, (long)PAGE_NOACCESS, (long)PAGE_EXECUTE, (long)msize);
+        return FALSE;
+    }
+    *nSize = memInfo.RegionSize;
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "exit HasReadAccess(addr = 0x%lx size = 0x%lx type=0x%lx\n", (long)pAddress, (long)(memInfo.RegionSize), (long)memInfo.Type);
+#endif //DEBUG_GETENV
+    return TRUE;
+}
+
+
+typedef struct PEB64 {
+    BYTE Reserved1[2];
+    BYTE BeingDebugged;
+    BYTE Reserved2[21];
+    PPEB_LDR_DATA LoaderData;
+    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
+    //BYTE Reserved3[520];
+    BYTE r3[144];
+    DWORD OSMajor;  // Warning - OSMajor/Minor/Build don't seem to be correct.
+    DWORD OSMinor;
+    WORD  OSBuild;
+    WORD  r4;
+    DWORD OSPlatformID;
+    DWORD r5[3];
+    BYTE  r6[252];
+    PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
+    BYTE Reserved4[136];
+    ULONG SessionId;
+} PEB64;
+
+typedef struct RTL_USER_PROCESS_PARAMETERS64 {
+    BYTE Reserved1[16];
+    BYTE Reserved2[0x28];
+    UNICODE_STRING CurrentWorkingDirectory; // 0x38
+    BYTE r3[0x18];
+    UNICODE_STRING ImagePathName; // 0x60
+    UNICODE_STRING CommandLine;   // 0x70
+    PVOID EnvAddr;                // 0x80
+  } RTL_USER_PROCESS_PARAMETERS64 ;
+
 /*
+ * Caveat - does not check for invalid structures in future (or past) versions of Windows
+ *
+ * callmode = 0 returns DirectByteBuffer, 1 = String cwd, 2 = String execuatable, 3 = String command line
+ *
  * Class:     com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl
  * Method:    getEnvironment0
  * Signature: ()[Ljava/lang/String;
  */
-JNIEXPORT jobjectArray JNICALL
+JNIEXPORT jobject JNICALL
 Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getEnvironment0
-  (JNIEnv *env, jclass winHelperClass, jint pid)
-{
-    // TODO - implement this stub - not eay (have to open the process memory and poke around)
-    // for now, return an empty array
-    jobjectArray ret = (jobjectArray)(*env)->NewObjectArray(env, 0, (*env)->FindClass(env, "java/lang/String"), (*env)->NewStringUTF(env, ""));
-    return ret;
+  (JNIEnv *env, jclass winHelperClass, jlong hProcess, jint callmode) {
+    PROCESS_BASIC_INFORMATION procBasicInfo = { 0 };
+
+    ULONG uReturnLength = 0;
+    NTSTATUS ntStat = QueryInformationProcesss((HANDLE)hProcess,
+        ProcessBasicInformation,
+        &procBasicInfo,
+        sizeof(procBasicInfo),
+        &uReturnLength);
+
+    if (ntStat != 0) {
+        fprintf(stderr, "QueryInformationProcess() returns error 0x%0lx (id=%ld retlen=%ld PBI size=%ld)\n", (long)ntStat,
+            (long)procBasicInfo.UniqueProcessId, (long)uReturnLength, (long)sizeof(procBasicInfo));
+    }
+
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "QueryInformationProcesss() ret=%ld id=%ld retlen=%ld PBI size=%ld\n",
+        (long)ntStat, (long)procBasicInfo.UniqueProcessId, (long)uReturnLength, (long)sizeof(procBasicInfo));
+#endif //DEBUG_GETENV
+
+    // Read the process environment block
+    PEB64 procEnvBlock = { 0 };
+    SIZE_T returnByteCount = 0;
+
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "reading PEB64 at 0x%ld pebsize=%ld\n", (long)procBasicInfo.PebBaseAddress, (long)sizeof(procEnvBlock));
+#endif //DEBUG_GETENV
+
+    // read the PEB from the other process (which is assumed to be 64-bit)
+    if (!ReadProcessMemory((HANDLE)hProcess,(LPCVOID)procBasicInfo.PebBaseAddress, &procEnvBlock, sizeof(procEnvBlock), &returnByteCount)) {
+        fprintf(stderr, "Error Reading Process Memory err=%ld", GetLastError());
+        return NULL;
+    }
+
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "read PEB64 at 0x%ld size 0x%ld ppaddr=0x%ld\n", (long)procBasicInfo.PebBaseAddress,
+        (long)returnByteCount, (long)procEnvBlock.ProcessParameters);
+    fprintf(stderr, "OS is %d.%d build %d platform 0x%lx\n", procEnvBlock.OSMajor, procEnvBlock.OSMinor, procEnvBlock.OSBuild, (long)procEnvBlock.OSPlatformID);
+#endif //DEBUG_GETENV
+
+    // Get the address of RTL_USER_PROCESS_PARAMETERS structure
+    UCHAR* puPEB = (UCHAR*)&procEnvBlock;
+    // retrieve a 64-bit pointer (to an usnsigned char) from PEB + 0x20
+    // this code appears to retrieve a 32 bit pointer
+    UCHAR* pRTLUserInfo = (UCHAR*) *((UINT_PTR *)(puPEB + 0x20));
+    int readableSize = 0;
+
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "testing RTL_USER_PROCESS_PARAMETERS memory at 0x%ld\n", (long)pRTLUserInfo);
+#endif //DEBUG_GETENV
+
+    if (!checkReadAccess((HANDLE)hProcess, pRTLUserInfo, &readableSize)) {
+        fprintf(stderr, "Error Reading Process Memory err=%ld", GetLastError());
+        return NULL;
+    }
+
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "zzz memory at 0x%ld size=0x%ld\n", (long)pRTLUserInfo, (long)readableSize);
+    fprintf(stderr, "reading RTLUserInfo at 0x%ld size 0x%ld\n", (long)pRTLUserInfo, (long)sizeof(RTL_USER_PROCESS_PARAMETERS64));
+#endif //DEBUG_GETENV
+
+    // Get the first 0x64 bytes of RTL_USER_PROCESS_PARAMETERS strcuture
+    RTL_USER_PROCESS_PARAMETERS64 upp = {0};
+    if (!ReadProcessMemory((HANDLE)hProcess, (LPCVOID)pRTLUserInfo, &upp, sizeof(RTL_USER_PROCESS_PARAMETERS64), &returnByteCount)) {
+        fprintf(stderr, "Error Reading Process Memory err=%ld", GetLastError());
+        return NULL;
+    }
+
+#if defined(DEBUG_GETENV)
+    fprintf(stderr, "have read RTLUserInfo at 0x%ld size 0x%ld\n", (long)pRTLUserInfo, (long)returnByteCount);
+#endif //DEBUG_GETENV
+
+#if defined(DEBUG_GETENV)
+    unsigned short* ch = (unsigned short*)(&upp);
+    for (int i = 0; i < sizeof(RTL_USER_PROCESS_PARAMETERS64) / 2; i++) {
+        fprintf(stderr, "  0x%04x = 0x%04x\n", i*2, ch[i]);
+    }
+#endif //DEBUG_GETENV
+
+    // cwd
+    if (callmode == 1) {
+#if defined(DEBUG_GETENV)
+        fprintf(stderr, "reading CurrentWorkingDirectory string at 0x%lx length %ld\n", (long)upp.CurrentWorkingDirectory.Buffer, (long)upp.CurrentWorkingDirectory.Length);
+#endif //DEBUG_GETENV
+        WCHAR* sb = malloc(upp.CurrentWorkingDirectory.Length);
+        if (!ReadProcessMemory((HANDLE)hProcess, (LPCVOID)upp.CurrentWorkingDirectory.Buffer, sb, upp.CurrentWorkingDirectory.Length, &returnByteCount)) {
+            fprintf(stderr, "Error Reading Process Memory err=%ld bytes=%ld\n", GetLastError(), (long)returnByteCount);
+            free(sb);
+            return NULL;
+        }
+        jstring s = (*env)->NewString(env, (const jchar *)sb, (jsize)upp.CurrentWorkingDirectory.Length / 2);
+        free(sb);
+        return s;
+    }
+
+    // executable image path
+    if (callmode == 2) {
+#if defined(DEBUG_GETENV)
+        fprintf(stderr, "reading string at 0x%lx length %ld\n", (long)upp.ImagePathName.Buffer, (long)upp.ImagePathName.Length);
+#endif //DEBUG_GETENV
+        WCHAR* sb = malloc(upp.ImagePathName.Length);
+        if (!ReadProcessMemory((HANDLE)hProcess, (LPCVOID)upp.ImagePathName.Buffer, sb, upp.ImagePathName.Length, &returnByteCount)) {
+            fprintf(stderr, "Error Reading Process Memory err=%ld bytes=%ld\n", GetLastError(), (long)returnByteCount);
+            free(sb);
+            return NULL;
+        }
+        jstring s = (*env)->NewString(env, (const jchar *)sb, (jsize)upp.ImagePathName.Length / 2);
+        free(sb);
+        return s;
+    }
+
+    // command line
+    if (callmode == 3) {
+#if defined(DEBUG_GETENV)
+        fprintf(stderr, "reading string at 0x%lx length %ld\n", (long)upp.CommandLine.Buffer, (long)upp.CommandLine.Length);
+#endif //DEBUG_GETENV
+        WCHAR* sb = malloc(upp.CommandLine.Length);
+        if (!ReadProcessMemory((HANDLE)hProcess, (LPCVOID)upp.CommandLine.Buffer, sb, upp.CommandLine.Length, &returnByteCount)) {
+            fprintf(stderr, "Error Reading Process Memory err=%ld bytes=%ld\n", GetLastError(), (long)returnByteCount);
+            free(sb);
+            return NULL;
+        }
+        jstring s = (*env)->NewString(env, (const jchar *)sb, (jsize)upp.CommandLine.Length / 2);
+        free(sb);
+        return s;
+    }
+
+    // Get the value at offset 0x48 to get the pointer to environment string block
+    if (callmode == 0) {
+        WCHAR* strPtr = (WCHAR*)(upp.EnvAddr);
+
+        // find out how much we can read, and then read it
+        // this will read more than just the environment, but there's not a lot we can do cleanly
+        if (!checkReadAccess((HANDLE)hProcess, strPtr, &readableSize)) {
+            fprintf(stderr, "Error Reading Process Memory err=%ld siz=0x%lx", GetLastError(), (long)readableSize);
+            return NULL;
+        }
+        // constrain readableSize to some maximum
+        // 0x38000 was observed on a windows 10 system (it's about 229K, so it's huge)
+        static const int MAX_ENV_BUFFER_SIZE = 0x38000;
+        if (readableSize > MAX_ENV_BUFFER_SIZE) {
+            readableSize = MAX_ENV_BUFFER_SIZE;
+        }
+#if defined(DEBUG_GETENV)
+        fprintf(stderr, "reading string at 0x%lx length %ld\n", (long)strPtr, (long)readableSize);
+#endif //DEBUG_GETENV
+        WCHAR* sb = malloc(readableSize);
+        if (!ReadProcessMemory((HANDLE)hProcess, (LPCVOID)strPtr, sb, readableSize, &returnByteCount)) {
+            fprintf(stderr, "Error Reading Process Memory err=%ld bytes=%ld\n", GetLastError(), (long)returnByteCount);
+            free(sb);
+            return NULL;
+        }
+#if defined(DEBUG_GETENV)
+            unsigned char* ch = (unsigned char*)(sb);
+            for (int i = 0; i < 100; i++) {
+                fprintf(stderr, "  0x%04x = 0x%02x\n", i, ch[i]);
+            }
+#endif //DEBUG_GETENV
+        // NOTE - consider ignoring error 299 (incomplete read) and just return returnByteCount bytes here.
+        jobject bytebuffer = (*env)->NewDirectByteBuffer(env, sb, readableSize);
+        return bytebuffer;
+    }
+
+    return NULL;
 }
 
-static unsigned __int64 convertFileTimeToInt64( const FILETIME * pFileTime )
-{
+static unsigned __int64 convertFileTimeToInt64( const FILETIME * pFileTime ) {
   ULARGE_INTEGER largeInt;
 
   largeInt.LowPart = pFileTime->dwLowDateTime;
@@ -410,8 +675,7 @@
  * Signature: (I[J)V
  */
 JNIEXPORT jboolean JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getProcessInfo0
-  (JNIEnv *env, jclass winHelperClass, jint pid, jlongArray array)
-{
+  (JNIEnv *env, jclass winHelperClass, jint pid, jlongArray array) {
     testLength(env, array, 4);
 
     HANDLE hProcess;
@@ -425,7 +689,7 @@
     jlong* data = (*env)->GetLongArrayElements(env, array, 0);
 
     pmc.cb = sizeof(pmc);
-    if ( GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc)) ) {
+    if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
         data[0] = pmc.WorkingSetSize;
     }
     else {
@@ -438,7 +702,7 @@
     FILETIME kernelTime;
     FILETIME userTime;
 
-    if ( GetProcessTimes( hProcess, &creationTime, &exitTime, &kernelTime, &userTime ) ) {
+    if (GetProcessTimes( hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
         // times returned from GetProcessTimes() are in 100-nanosecond units.
         data[1] = convertFileTimeToInt64(&userTime);
         data[2] = convertFileTimeToInt64(&kernelTime);
@@ -458,18 +722,17 @@
  * Signature: (I[J)V
  */
 JNIEXPORT jboolean JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getProcessIOInfo0
-  (JNIEnv *env, jclass winHelperClass, jint pid, jlongArray array)
-{
+  (JNIEnv *env, jclass winHelperClass, jint pid, jlongArray array) {
     testLength(env, array, 6);
 
     HANDLE hProcess;
     IO_COUNTERS iocounters;
 
-    hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid );
+    hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
     if (NULL == hProcess)
         return FALSE;
 
-    BOOL rc = GetProcessIoCounters( hProcess, &iocounters );
+    BOOL rc = GetProcessIoCounters(hProcess, &iocounters);
     if (!rc) {
         CloseHandle(hProcess);
         return FALSE;
@@ -512,19 +775,30 @@
 JNIEXPORT jlong JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getProcessHandle0
   (JNIEnv *env, jclass winHelperClass, jint pid) {
 
-    HANDLE hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid );
+    HANDLE hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
     return (jlong) hProcess;
 }
 
 /*
  * Class:     com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl
+ * Method:    getCurrentProcessID0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getCurrentProcessID0
+  (JNIEnv *env, jclass winHelperClass) {
+
+    return (jint) GetCurrentProcessId();
+}
+
+/*
+ * Class:     com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl
  * Method:    getLimitedProcessHandle0
  * Signature: (I)J
  */
 JNIEXPORT jlong JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_getLimitedProcessHandle0
   (JNIEnv *env, jclass winHelperClass, jint pid) {
 
-    HANDLE hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid );
+    HANDLE hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
     return (jlong) hProcess;
 }
 
@@ -534,7 +808,7 @@
  * Signature: (J)V
  */
 JNIEXPORT void JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_closeHandle0
-  (JNIEnv *env, jclass winHelperClass, jlong handle){
+  (JNIEnv *env, jclass winHelperClass, jlong handle) {
 
     CloseHandle((HANDLE)handle);
 }
@@ -547,7 +821,7 @@
 JNIEXPORT jboolean JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_terminateProcess0
   (JNIEnv *env, jclass winHelperClass, jint pid, jint exitCode, jint waitMillis) {
 
-    HANDLE hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid );
+    HANDLE hProcess = (pid == 0) ? GetCurrentProcess() : OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
     if (!hProcess) {
         return FALSE;
     }
@@ -560,3 +834,14 @@
 }
 
 
+/*
+ * Class:     Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl
+ * Method:    freeDirectBuffer0
+ * Signature: (Ljava/nio/ByteBuffer;)V
+ */
+JNIEXPORT void JNICALL Java_com_redhat_thermostat_common_portability_internal_windows_WindowsHelperImpl_freeDirectBuffer0
+  (JNIEnv *env, jobject obj, jobject bytebuffer) {
+
+    void *buffer = (*env)->GetDirectBufferAddress(env, bytebuffer);
+    free(buffer);
+}
--- a/common/portability/src/test/java/com/redhat/thermostat/common/portability/internal/windows/WindowsHelperImplTest.java	Mon Feb 27 17:13:32 2017 +0100
+++ b/common/portability/src/test/java/com/redhat/thermostat/common/portability/internal/windows/WindowsHelperImplTest.java	Tue Feb 28 14:33:51 2017 -0500
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.common.portability.internal.windows;
 
+import com.redhat.thermostat.shared.config.NativeLibraryResolver;
 import com.redhat.thermostat.shared.config.OS;
 
+import com.redhat.thermostat.shared.config.internal.CommonPathsImpl;
 import org.junit.Assume;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -95,4 +97,79 @@
         assertNotNull(s);
         assertFalse(s.isEmpty());
     }
+
+    private static void dumpProcess(int pid) {
+
+        final WindowsHelperImpl helper = WindowsHelperImpl.INSTANCE;
+
+        System.out.println("pid " + pid + " user=" + helper.getUserName(pid));
+
+        final String cwd = helper.getCWD(pid);
+        final String exec = helper.getExecutable(pid);
+        final String cmd = helper.getCommandLine(pid);
+
+        if (cwd != null) {
+            System.out.println("pid " + pid + " cwd = " + cwd);
+        } else {
+            System.out.println("pid " + pid + " cwd is not available");
+        }
+
+        if (exec != null) {
+            System.out.println("pid " + pid + " exec = " + exec);
+        } else {
+            System.out.println("pid " + pid + " exec is not available");
+        }
+
+        if (cmd != null) {
+            System.out.println("pid " + pid + " cmd = " + cmd);
+        } else {
+            System.out.println("pid " + pid + " cmd is not available");
+        }
+
+        Map<String, String> env = helper.getEnvironment(pid);
+        if (env != null && !env.isEmpty()) {
+            for (final Map.Entry<String,String> var : env.entrySet()) {
+                System.out.println(var.getKey() + " = " + var.getValue());
+            }
+        }
+        else {
+            System.out.println("env for pid " + pid + " is not available");
+        }
+    }
+    // manual sanity test
+    public static void main( String[] args ) {
+        if (System.getenv("THERMOSTAT_HOME") == null) {
+            // fix up for CommonPaths
+            System.setProperty("THERMOSTAT_HOME", "/");
+            System.setProperty("USER_THERMOSTAT_HOME", "/");
+        }
+        NativeLibraryResolver.setCommonPaths(new CommonPathsImpl());
+        final WindowsHelperImpl helper = WindowsHelperImpl.INSTANCE;
+
+        System.out.println("Hostname = " + helper.getHostName());
+        System.out.println("OS version = " + helper.getOSVersion());
+        System.out.println("CPU = " + helper.getCPUModel());
+        System.out.println("OS name = " + helper.getOSName());
+
+        System.out.println("Current process = " + helper.getCurrentProcessPid());
+
+        final int pid = args.length > 0 ? Integer.parseInt(args[0]) : 0;
+
+        dumpProcess(pid);
+
+        System.out.println("\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
+
+        dumpProcess(0);
+
+        System.out.println("\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
+
+        Map<String, String> myenv = System.getenv();
+        for (String envName : myenv.keySet()) {
+            System.out.format("%s = %s%n",
+                    envName,
+                    myenv.get(envName));
+        }
+
+    }
+
 }