Changeset 57916 in vbox
- Timestamp:
- Sep 27, 2015 8:36:38 PM (9 years ago)
- Location:
- trunk
- Files:
-
- 6 edited
-
include/iprt/process.h (modified) (2 diffs)
-
src/VBox/Runtime/Makefile.kmk (modified) (1 diff)
-
src/VBox/Runtime/r3/posix/process-posix.cpp (modified) (1 diff)
-
src/VBox/Runtime/r3/win/process-win.cpp (modified) (18 diffs)
-
src/VBox/Runtime/testcase/tstRTProcQueryUsername.cpp (modified) (1 diff)
-
src/VBox/Runtime/win/RTErrConvertFromWin32.cpp (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/include/iprt/process.h
r57870 r57916 392 392 * @retval VERR_BUFFER_OVERFLOW if the given buffer size is to small for the username. 393 393 * @param hProcess The process handle to query the username for. 394 * NIL_PROCESS is an alias for the current process. 394 395 * @param pszUser Where to store the user name on success. 395 396 * @param cbUser The size of the user name buffer. … … 398 399 * is returned. 399 400 */ 400 RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, 401 size_t *pcbUser); 401 RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser); 402 402 403 403 /** -
trunk/src/VBox/Runtime/Makefile.kmk
r57905 r57916 599 599 generic/RTPathGetCurrentDrive-generic.cpp \ 600 600 generic/RTPathIsSame-generic.cpp \ 601 generic/RTProcessQueryUsernameA-generic.cpp \602 601 generic/RTTimerLRCreate-generic.cpp \ 603 602 generic/mempool-generic.cpp \ -
trunk/src/VBox/Runtime/r3/posix/process-posix.cpp
r57358 r57916 159 159 160 160 161 RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, 162 size_t *pcbUser) 161 RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser) 163 162 { 164 163 AssertReturn( (pszUser && cbUser > 0) 165 164 || (!pszUser && !cbUser), VERR_INVALID_PARAMETER); 166 167 if (hProcess != RTProcSelf()) 168 return VERR_NOT_SUPPORTED; 169 170 int32_t cbPwdMax = sysconf(_SC_GETPW_R_SIZE_MAX); 171 if (cbPwdMax == -1) 172 return RTErrConvertFromErrno(errno); 173 174 char *pbBuf = (char *)RTMemAllocZ(cbPwdMax); 175 if (!pbBuf) 176 return VERR_NO_MEMORY; 177 178 struct passwd Pwd, *pPwd; 179 int rc = getpwuid_r(geteuid(), &Pwd, pbBuf, cbPwdMax, &pPwd); 180 if (!rc) 181 { 182 size_t cbPwdUser = strlen(pPwd->pw_name) + 1; 183 184 if (pcbUser) 185 *pcbUser = cbPwdUser; 186 187 if (cbPwdUser > cbUser) 188 rc = VERR_BUFFER_OVERFLOW; 189 else 165 AssertReturn(pcbUser || pszUser, VERR_INVALID_PARAMETER); 166 167 int rc; 168 if ( hProcess == NIL_RTPROCESS 169 || hProcess == RTProcSelf()) 170 { 171 /* 172 * Figure a good buffer estimate. 173 */ 174 int32_t cbPwdMax = sysconf(_SC_GETPW_R_SIZE_MAX); 175 if (cbPwdMax <= sizeof(_1K)) 176 cbPwdMax = _1K; 177 else 178 AssertStmt(cbPwdMax <= 32U*_1M, cbPwdMax = 32U*_1M); 179 char *pchBuf = (char *)RTMemTmpAllocZ(cbPwdMax); 180 if (pbBuf) 190 181 { 191 /** @todo this needs to be UTF-8 checked or converted... */ 192 memcpy(pszUser, pPwd->pw_name, cbPwdUser); 193 rc = VINF_SUCCESS; 182 /* 183 * Get the password file entry. 184 */ 185 struct passwd Pwd; 186 struct passwd *pPwd = NULL; 187 rc = getpwuid_r(geteuid(), &Pwd, pchBuf, cbPwdMax, &pPwd); 188 if (!rc) 189 { 190 /* 191 * Convert the name to UTF-8, assuming that we're getting it in the local codeset. 192 */ 193 /** @todo This isn't exactly optimal... the current codeset/page conversion 194 * stuff never was. Should optimize that for UTF-8 and ASCII one day. 195 * And also optimize for avoiding heap. */ 196 char *pszTmp = NULL; 197 rc = RTStrCurrentCPToUtf8(&pszTmp, pPwd->pw_name); 198 if (RT_SUCCESS(rc)) 199 { 200 size_t cbTmp = strlen(pszTmp) + 1; 201 if (pcbUser) 202 *pcbUser = cbTmp; 203 if (cbPwdUser <= cbUser) 204 { 205 memcpy(pszUser, pszTmp, cbTmp); 206 rc = VINF_SUCCESS; 207 } 208 else 209 rc = VERR_BUFFER_OVERFLOW; 210 RTStrFree(pszTmp); 211 } 212 } 213 else 214 rc = RTErrConvertFromErrno(rc); 215 RTMemFree(pchBuf); 194 216 } 217 else 218 rc = VERR_NO_TMP_MEMORY; 195 219 } 196 220 else 197 rc = RTErrConvertFromErrno(rc); 198 199 RTMemFree(pbBuf); 221 rc = VERR_NOT_SUPPORTED; 200 222 return rc; 201 223 } 202 224 225 226 RTR3DECL(int) RTProcQueryUsernameA(RTPROCESS hProcess, char **ppszUser) 227 { 228 AssertPtrReturn(ppszUser, VERR_INVALID_POINTER); 229 230 int rc; 231 if ( hProcess == NIL_RTPROCESS 232 || hProcess == RTProcSelf()) 233 { 234 /* 235 * Figure a good buffer estimate. 236 */ 237 int32_t cbPwdMax = sysconf(_SC_GETPW_R_SIZE_MAX); 238 if (cbPwdMax <= sizeof(_1K)) 239 cbPwdMax = _1K; 240 else 241 AssertStmt(cbPwdMax <= 32U*_1M, cbPwdMax = 32U*_1M); 242 char *pchBuf = (char *)RTMemTmpAllocZ(cbPwdMax); 243 if (pbBuf) 244 { 245 /* 246 * Get the password file entry. 247 */ 248 struct passwd Pwd; 249 struct passwd *pPwd = NULL; 250 rc = getpwuid_r(geteuid(), &Pwd, pchBuf, cbPwdMax, &pPwd); 251 if (!rc) 252 { 253 /* 254 * Convert the name to UTF-8, assuming that we're getting it in the local codeset. 255 */ 256 rc = RTStrCurrentCPToUtf8(ppszUser, pPwd->pw_name); 257 } 258 else 259 rc = RTErrConvertFromErrno(rc); 260 RTMemFree(pchBuf); 261 } 262 else 263 rc = VERR_NO_TMP_MEMORY; 264 } 265 else 266 rc = VERR_NOT_SUPPORTED; 267 return rc; 268 } 269 -
trunk/src/VBox/Runtime/r3/win/process-win.cpp
r57906 r57916 360 360 361 361 /** 362 * Map some important or much used Windows error codes363 * to our error codes.364 *365 * @return Mapped IPRT status code.366 * @param dwError Windows error code to map to IPRT code.367 */368 static int rtProcWinMapErrorCodes(DWORD dwError)369 {370 int rc;371 switch (dwError)372 {373 case ERROR_NOACCESS: /** @todo r=bird: this is a bogus transation. Used a couple of places in main. */374 rc = VERR_PERMISSION_DENIED;375 break;376 377 default:378 /* Could trigger a debug assertion! */379 rc = RTErrConvertFromWin32(dwError);380 break;381 }382 return rc;383 }384 385 386 /**387 362 * Get the process token of the process indicated by @a dwPID if the @a pSid 388 363 * matches. … … 420 395 if (pTokenUser) 421 396 { 422 if (GetTokenInformation(hTokenProc, 423 TokenUser, 424 pTokenUser, 425 dwSize, 426 &dwSize)) 397 if (GetTokenInformation(hTokenProc, TokenUser, pTokenUser, dwSize, &dwSize)) 427 398 { 428 399 if ( IsValidSid(pTokenUser->User.Sid) … … 440 411 } 441 412 else 442 rc = rtProcWinMapErrorCodes(GetLastError());413 rc = RTErrConvertFromWin32(GetLastError()); 443 414 } 444 415 else … … 446 417 } 447 418 else 448 rc = rtProcWinMapErrorCodes(GetLastError());419 rc = RTErrConvertFromWin32(GetLastError()); 449 420 RTMemTmpFree(pTokenUser); 450 421 } … … 455 426 rc = VERR_IPE_UNEXPECTED_STATUS; 456 427 else 457 rc = rtProcWinMapErrorCodes(dwErr);428 rc = RTErrConvertFromWin32(dwErr); 458 429 CloseHandle(hTokenProc); 459 430 } 460 431 else 461 rc = rtProcWinMapErrorCodes(GetLastError());432 rc = RTErrConvertFromWin32(GetLastError()); 462 433 CloseHandle(hProc); 463 434 } 464 435 else 465 rc = rtProcWinMapErrorCodes(GetLastError());436 rc = RTErrConvertFromWin32(GetLastError()); 466 437 return rc; 467 438 } … … 661 632 662 633 DWORD dwErr = GetLastError(); 663 int rc = dwErr == ERROR_PRIVILEGE_NOT_HELD ? VERR_PROC_TCB_PRIV_NOT_HELD : rtProcWinMapErrorCodes(dwErr);634 int rc = dwErr == ERROR_PRIVILEGE_NOT_HELD ? VERR_PROC_TCB_PRIV_NOT_HELD : RTErrConvertFromWin32(dwErr); 664 635 if (rc == VERR_UNRESOLVED_ERROR) 665 636 LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc)); … … 793 764 AssertFailed(); 794 765 return VERR_PRIVILEGE_NOT_HELD; 766 } 767 768 #if 0 /* debug code */ 769 770 static char *rtProcWinSidToString(char *psz, PSID pSid) 771 { 772 char *pszRet = psz; 773 774 *psz++ = 'S'; 775 *psz++ = '-'; 776 *psz++ = '1'; 777 *psz++ = '-'; 778 779 PISID pISid = (PISID)pSid; 780 781 psz += RTStrFormatU32(psz, 32, RT_MAKE_U32_FROM_U8(pISid->IdentifierAuthority.Value[5], 782 pISid->IdentifierAuthority.Value[4], 783 pISid->IdentifierAuthority.Value[3], 784 pISid->IdentifierAuthority.Value[2]), 785 10, 0, 0, 0); 786 for (unsigned i = 0; i < pISid->SubAuthorityCount; i++) 787 { 788 *psz++ = '-'; 789 psz += RTStrFormatU32(psz, 32, pISid->SubAuthority[i], 10, 0, 0, 0); 790 } 791 *psz++ = '\0'; 792 return pszRet; 793 } 794 795 static void rtProcWinLogAcl(PACL pAcl) 796 { 797 if (!pAcl) 798 RTAssertMsg2("ACL is NULL\n"); 799 else 800 { 801 RTAssertMsg2("AceCount=%d AclSize=%#x AclRevision=%d\n", pAcl->AceCount, pAcl->AclSize, pAcl->AclRevision); 802 for (uint32_t i = 0; i < pAcl->AceCount; i++) 803 { 804 PACE_HEADER pAceHdr = NULL; 805 if (GetAce(pAcl, i, (PVOID *)&pAceHdr)) 806 { 807 RTAssertMsg2(" ACE[%u]: Flags=%#x Type=%#x Size=%#x", i, pAceHdr->AceFlags, pAceHdr->AceType, pAceHdr->AceSize); 808 char szTmp[256]; 809 if (pAceHdr->AceType == ACCESS_ALLOWED_ACE_TYPE) 810 RTAssertMsg2(" Mask=%#x %s\n", ((ACCESS_ALLOWED_ACE *)pAceHdr)->Mask, 811 rtProcWinSidToString(szTmp, &((ACCESS_ALLOWED_ACE *)pAceHdr)->SidStart)); 812 else 813 RTAssertMsg2(" ACE[%u]: Flags=%#x Type=%#x Size=%#x\n", i, pAceHdr->AceFlags, pAceHdr->AceType, pAceHdr->AceSize); 814 } 815 } 816 } 817 } 818 819 static bool rtProcWinLogSecAttr(HANDLE hUserObj) 820 { 821 /* 822 * Get the security descriptor for the user interface object. 823 */ 824 uint32_t cbSecDesc = _64K; 825 PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); 826 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; 827 DWORD cbNeeded; 828 AssertReturn(pSecDesc, false); 829 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) 830 { 831 RTMemTmpFree(pSecDesc); 832 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, false); 833 cbSecDesc = cbNeeded + 128; 834 pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); 835 AssertReturn(pSecDesc, false); 836 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) 837 { 838 RTMemTmpFree(pSecDesc); 839 AssertFailedReturn(false); 840 } 841 } 842 843 /* 844 * Get the discretionary access control list (if we have one). 845 */ 846 BOOL fDaclDefaulted; 847 BOOL fDaclPresent; 848 PACL pDacl; 849 if (GetSecurityDescriptorDacl(pSecDesc, &fDaclPresent, &pDacl, &fDaclDefaulted)) 850 rtProcWinLogAcl(pDacl); 851 else 852 RTAssertMsg2("GetSecurityDescriptorDacl failed\n"); 853 854 RTMemFree(pSecDesc); 855 return true; 856 } 857 858 #endif /* debug */ 859 860 /** 861 * Get the user SID from a token. 862 * 863 * @returns Pointer to the SID on success. Free by calling RTMemFree. 864 * @param hToken The token.. 865 */ 866 static PSID rtProcWinGetTokenUserSid(HANDLE hToken) 867 { 868 /* 869 * Get the groups associated with the token. We just try a size first then 870 * reallocates if it's insufficient. 871 */ 872 DWORD cbUser = _1K; 873 PTOKEN_USER pUser = (PTOKEN_USER)RTMemTmpAlloc(cbUser); 874 AssertReturn(pUser, NULL); 875 DWORD cbNeeded = 0; 876 if (!GetTokenInformation(hToken, TokenUser, pUser, cbUser, &cbNeeded)) 877 { 878 RTMemTmpFree(pUser); 879 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL); 880 cbUser = cbNeeded + 128; 881 pUser = (PTOKEN_USER)RTMemTmpAlloc(cbUser); 882 AssertReturn(pUser, NULL); 883 if (!GetTokenInformation(hToken, TokenUser, pUser, cbUser, &cbNeeded)) 884 { 885 RTMemTmpFree(pUser); 886 AssertFailedReturn(NULL); 887 } 888 } 889 890 DWORD cbSid = GetLengthSid(pUser->User.Sid); 891 PSID pSidRet = RTMemDup(pUser->User.Sid, cbSid); 892 Assert(pSidRet); 893 RTMemTmpFree(pUser); 894 return pSidRet; 895 } 896 897 898 #if 0 /* not used */ 899 /** 900 * Get the login SID from a token. 901 * 902 * @returns Pointer to the SID on success. Free by calling RTMemFree. 903 * @param hToken The token.. 904 */ 905 static PSID rtProcWinGetTokenLogonSid(HANDLE hToken) 906 { 907 /* 908 * Get the groups associated with the token. We just try a size first then 909 * reallocates if it's insufficient. 910 */ 911 DWORD cbGroups = _1K; 912 PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbGroups); 913 AssertReturn(pGroups, NULL); 914 DWORD cbNeeded = 0; 915 if (!GetTokenInformation(hToken, TokenGroups, pGroups, cbGroups, &cbNeeded)) 916 { 917 RTMemTmpFree(pGroups); 918 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL); 919 cbGroups = cbNeeded + 128; 920 pGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbGroups); 921 AssertReturn(pGroups, NULL); 922 if (!GetTokenInformation(hToken, TokenGroups, pGroups, cbGroups, &cbNeeded)) 923 { 924 RTMemTmpFree(pGroups); 925 AssertFailedReturn(NULL); 926 } 927 } 928 929 /* 930 * Locate the logon sid. 931 */ 932 PSID pSidRet = NULL; 933 uint32_t i = pGroups->GroupCount; 934 while (i-- > 0) 935 if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) 936 { 937 DWORD cbSid = GetLengthSid(pGroups->Groups[i].Sid); 938 pSidRet = RTMemDup(pGroups->Groups[i].Sid, cbSid); 939 break; 940 } 941 942 RTMemTmpFree(pGroups); 943 Assert(pSidRet); 944 return pSidRet; 945 } 946 #endif /* unused */ 947 948 949 /** 950 * Retrieves the DACL security descriptor of the give GUI object. 951 * 952 * @returns Pointer to the security descriptor. 953 * @param hUserObj The GUI object handle. 954 * @param pcbSecDesc Where to return the size of the security descriptor. 955 * @param ppDacl Where to return the DACL pointer. 956 * @param pfDaclPresent Where to return the DACL-present indicator. 957 * @param pDaclSizeInfo Where to return the DACL size information. 958 */ 959 static PSECURITY_DESCRIPTOR rtProcWinGetUserObjDacl(HANDLE hUserObj, uint32_t *pcbSecDesc, PACL *ppDacl, 960 BOOL *pfDaclPresent, ACL_SIZE_INFORMATION *pDaclSizeInfo) 961 { 962 /* 963 * Get the security descriptor for the user interface object. 964 */ 965 uint32_t cbSecDesc = _1K; 966 PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); 967 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; 968 DWORD cbNeeded; 969 AssertReturn(pSecDesc, NULL); 970 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) 971 { 972 RTMemTmpFree(pSecDesc); 973 AssertReturn(GetLastError() == ERROR_INSUFFICIENT_BUFFER, NULL); 974 cbSecDesc = cbNeeded + 128; 975 pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); 976 AssertReturn(pSecDesc, NULL); 977 if (!GetUserObjectSecurity(hUserObj, &SecInfo, pSecDesc, cbSecDesc, &cbNeeded)) 978 { 979 RTMemTmpFree(pSecDesc); 980 AssertFailedReturn(NULL); 981 } 982 } 983 *pcbSecDesc = cbNeeded; 984 985 /* 986 * Get the discretionary access control list (if we have one). 987 */ 988 BOOL fDaclDefaulted; 989 if (GetSecurityDescriptorDacl(pSecDesc, pfDaclPresent, ppDacl, &fDaclDefaulted)) 990 { 991 RT_ZERO(*pDaclSizeInfo); 992 pDaclSizeInfo->AclBytesInUse = sizeof(ACL); 993 if ( !*ppDacl 994 || GetAclInformation(*ppDacl, pDaclSizeInfo, sizeof(*pDaclSizeInfo), AclSizeInformation)) 995 return pSecDesc; 996 AssertFailed(); 997 } 998 else 999 AssertFailed(); 1000 RTMemTmpFree(pSecDesc); 1001 return NULL; 1002 } 1003 1004 1005 /** 1006 * Copy ACEs from one ACL to another. 1007 * 1008 * @returns true on success, false on failure. 1009 * @param pDst The destination ACL. 1010 * @param pSrc The source ACL. 1011 * @param cAces The number of ACEs to copy. 1012 */ 1013 static bool rtProcWinCopyAces(PACL pDst, PACL pSrc, uint32_t cAces) 1014 { 1015 for (uint32_t i = 0; i < cAces; i++) 1016 { 1017 PACE_HEADER pAceHdr; 1018 AssertReturn(GetAce(pSrc, i, (PVOID *)&pAceHdr), false); 1019 AssertReturn(AddAce(pDst, ACL_REVISION, MAXDWORD, pAceHdr, pAceHdr->AceSize), false); 1020 } 1021 return true; 1022 } 1023 1024 1025 /** 1026 * Adds an access-allowed access control entry to an ACL. 1027 * 1028 * @returns true on success, false on failure. 1029 * @param pDstAcl The ACL. 1030 * @param fAceFlags The ACE flags. 1031 * @param fMask The ACE access mask. 1032 * @param pSid The SID to go with the ACE. 1033 * @param cbSid The size of the SID. 1034 */ 1035 static bool rtProcWinAddAccessAllowedAce(PACL pDstAcl, uint32_t fAceFlags, uint32_t fMask, PSID pSid, uint32_t cbSid) 1036 { 1037 struct 1038 { 1039 ACCESS_ALLOWED_ACE Core; 1040 DWORD abPadding[128]; /* More than enough, AFAIK. */ 1041 } AceBuf; 1042 RT_ZERO(AceBuf); 1043 uint32_t const cbAllowedAce = RT_OFFSETOF(ACCESS_ALLOWED_ACE, SidStart) + cbSid; 1044 AssertReturn(cbAllowedAce <= sizeof(AceBuf), false); 1045 1046 AceBuf.Core.Header.AceSize = cbAllowedAce; 1047 AceBuf.Core.Header.AceType = ACCESS_ALLOWED_ACE_TYPE; 1048 AceBuf.Core.Header.AceFlags = fAceFlags; 1049 AceBuf.Core.Mask = fMask; 1050 AssertReturn(CopySid(cbSid, &AceBuf.Core.SidStart, pSid), false); 1051 1052 uint32_t i = pDstAcl->AceCount; 1053 while (i-- > 0) 1054 { 1055 PACE_HEADER pAceHdr; 1056 AssertStmt(GetAce(pDstAcl, i, (PVOID *)&pAceHdr), continue); 1057 if ( pAceHdr->AceSize == cbAllowedAce 1058 && memcmp(pAceHdr, &AceBuf.Core, cbAllowedAce) == 0) 1059 return true; 1060 1061 } 1062 AssertMsgReturn(AddAce(pDstAcl, ACL_REVISION, MAXDWORD, &AceBuf.Core, cbAllowedAce), ("%u\n", GetLastError()), false); 1063 return true; 1064 } 1065 1066 1067 /** All window station rights we know about */ 1068 #define MY_WINSTATION_ALL_RIGHTS ( WINSTA_ACCESSCLIPBOARD | WINSTA_ACCESSGLOBALATOMS | WINSTA_CREATEDESKTOP \ 1069 | WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | WINSTA_READATTRIBUTES \ 1070 | WINSTA_READSCREEN | WINSTA_WRITEATTRIBUTES | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER ) 1071 /** All desktop rights we know about */ 1072 #define MY_DESKTOP_ALL_RIGHTS ( DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL \ 1073 | DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_READOBJECTS \ 1074 | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS | DELETE | READ_CONTROL | WRITE_DAC \ 1075 | WRITE_OWNER ) 1076 /** Generic rights. */ 1077 #define MY_GENERIC_ALL_RIGHTS ( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL ) 1078 1079 1080 /** 1081 * Grants the given SID full access to the given window station. 1082 * 1083 * @returns true on success, false on failure. 1084 * @param hWinStation The window station. 1085 * @param pSid The SID. 1086 */ 1087 static bool rtProcWinAddSidToWinStation(HWINSTA hWinStation, PSID pSid) 1088 { 1089 bool fRet = false; 1090 1091 /* 1092 * Get the current DACL. 1093 */ 1094 uint32_t cbSecDesc; 1095 PACL pDacl; 1096 ACL_SIZE_INFORMATION DaclSizeInfo; 1097 BOOL fDaclPresent; 1098 PSECURITY_DESCRIPTOR pSecDesc = rtProcWinGetUserObjDacl(hWinStation, &cbSecDesc, &pDacl, &fDaclPresent, &DaclSizeInfo); 1099 if (pSecDesc) 1100 { 1101 /* 1102 * Create a new DACL. This will contain two extra ACEs. 1103 */ 1104 PSECURITY_DESCRIPTOR pNewSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); 1105 if ( pNewSecDesc 1106 && InitializeSecurityDescriptor(pNewSecDesc, SECURITY_DESCRIPTOR_REVISION)) 1107 { 1108 uint32_t const cbSid = GetLengthSid(pSid); 1109 uint32_t const cbNewDacl = DaclSizeInfo.AclBytesInUse + (sizeof(ACCESS_ALLOWED_ACE) + cbSid) * 2; 1110 PACL pNewDacl = (PACL)RTMemTmpAlloc(cbNewDacl); 1111 if ( pNewDacl 1112 && InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION) 1113 && rtProcWinCopyAces(pNewDacl, pDacl, fDaclPresent ? DaclSizeInfo.AceCount : 0)) 1114 { 1115 /* 1116 * Add the two new SID ACEs. 1117 */ 1118 if ( rtProcWinAddAccessAllowedAce(pNewDacl, CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE, 1119 MY_GENERIC_ALL_RIGHTS, pSid, cbSid) 1120 && rtProcWinAddAccessAllowedAce(pNewDacl, NO_PROPAGATE_INHERIT_ACE, MY_WINSTATION_ALL_RIGHTS, pSid, cbSid)) 1121 { 1122 /* 1123 * Now mate the new DECL with the security descriptor and set it. 1124 */ 1125 if (SetSecurityDescriptorDacl(pNewSecDesc, TRUE /*fDaclPresent*/, pNewDacl, FALSE /*fDaclDefaulted*/)) 1126 { 1127 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; 1128 if (SetUserObjectSecurity(hWinStation, &SecInfo, pNewSecDesc)) 1129 fRet = true; 1130 else 1131 AssertFailed(); 1132 } 1133 else 1134 AssertFailed(); 1135 } 1136 else 1137 AssertFailed(); 1138 } 1139 else 1140 AssertFailed(); 1141 RTMemTmpFree(pNewDacl); 1142 } 1143 else 1144 AssertFailed(); 1145 RTMemTmpFree(pNewSecDesc); 1146 RTMemTmpFree(pSecDesc); 1147 } 1148 return fRet; 1149 } 1150 1151 1152 /** 1153 * Grants the given SID full access to the given desktop. 1154 * 1155 * @returns true on success, false on failure. 1156 * @param hWinStation The window station. 1157 * @param pSid The SID. 1158 */ 1159 static bool rtProcWinAddSidToDesktop(HDESK hDesktop, PSID pSid) 1160 { 1161 bool fRet = false; 1162 1163 /* 1164 * Get the current DACL. 1165 */ 1166 uint32_t cbSecDesc; 1167 PACL pDacl; 1168 ACL_SIZE_INFORMATION DaclSizeInfo; 1169 BOOL fDaclPresent; 1170 PSECURITY_DESCRIPTOR pSecDesc = rtProcWinGetUserObjDacl(hDesktop, &cbSecDesc, &pDacl, &fDaclPresent, &DaclSizeInfo); 1171 if (pSecDesc) 1172 { 1173 /* 1174 * Create a new DACL. This will contain one extra ACE. 1175 */ 1176 PSECURITY_DESCRIPTOR pNewSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); 1177 if ( pNewSecDesc 1178 && InitializeSecurityDescriptor(pNewSecDesc, SECURITY_DESCRIPTOR_REVISION)) 1179 { 1180 uint32_t const cbSid = GetLengthSid(pSid); 1181 uint32_t const cbNewDacl = DaclSizeInfo.AclBytesInUse + (sizeof(ACCESS_ALLOWED_ACE) + cbSid) * 1; 1182 PACL pNewDacl = (PACL)RTMemTmpAlloc(cbNewDacl); 1183 if ( pNewDacl 1184 && InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION) 1185 && rtProcWinCopyAces(pNewDacl, pDacl, fDaclPresent ? DaclSizeInfo.AceCount : 0)) 1186 { 1187 /* 1188 * Add the new SID ACE. 1189 */ 1190 if (rtProcWinAddAccessAllowedAce(pNewDacl, 0 /*fAceFlags*/, MY_DESKTOP_ALL_RIGHTS, pSid, cbSid)) 1191 { 1192 /* 1193 * Now mate the new DECL with the security descriptor and set it. 1194 */ 1195 if (SetSecurityDescriptorDacl(pNewSecDesc, TRUE /*fDaclPresent*/, pNewDacl, FALSE /*fDaclDefaulted*/)) 1196 { 1197 SECURITY_INFORMATION SecInfo = DACL_SECURITY_INFORMATION; 1198 if (SetUserObjectSecurity(hDesktop, &SecInfo, pNewSecDesc)) 1199 fRet = true; 1200 else 1201 AssertFailed(); 1202 } 1203 else 1204 AssertFailed(); 1205 } 1206 else 1207 AssertFailed(); 1208 } 1209 else 1210 AssertFailed(); 1211 RTMemTmpFree(pNewDacl); 1212 } 1213 else 1214 AssertFailed(); 1215 RTMemTmpFree(pNewSecDesc); 1216 RTMemTmpFree(pSecDesc); 1217 } 1218 return fRet; 1219 } 1220 1221 1222 /** 1223 * Preps the window station and desktop for the new app. 1224 * 1225 * EXPERIMENTAL. Thus no return code. 1226 * 1227 * @param hTokenToUse The access token of the new process. 1228 * @param pStartupInfo The startup info (we'll change lpDesktop, maybe). 1229 * @param phWinStationOld Where to return an window station handle to restore. 1230 * Pass this to SetProcessWindowStation if not NULL. 1231 */ 1232 static void rtProcWinStationPrep(HANDLE hTokenToUse, STARTUPINFOW *pStartupInfo, HWINSTA *phWinStationOld) 1233 { 1234 /** @todo Always mess with the interactive one? Maybe it's not there... */ 1235 *phWinStationOld = GetProcessWindowStation(); 1236 HWINSTA hWinStation0 = OpenWindowStationW(L"winsta0", FALSE /*fInherit*/, READ_CONTROL | WRITE_DAC); 1237 if (hWinStation0) 1238 { 1239 if (SetProcessWindowStation(hWinStation0)) 1240 { 1241 HDESK hDesktop = OpenDesktop("default", 0 /*fFlags*/, FALSE /*fInherit*/, 1242 READ_CONTROL | WRITE_DAC | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); 1243 if (hDesktop) 1244 { 1245 /*PSID pSid = rtProcWinGetTokenLogonSid(hTokenToUse); - Better to use the user SID. Avoid overflowing the ACL. */ 1246 PSID pSid = rtProcWinGetTokenUserSid(hTokenToUse); 1247 if (pSid) 1248 { 1249 if ( rtProcWinAddSidToWinStation(hWinStation0, pSid) 1250 && rtProcWinAddSidToDesktop(hDesktop, pSid)) 1251 { 1252 pStartupInfo->lpDesktop = L"winsta0\\default"; 1253 } 1254 RTMemFree(pSid); 1255 } 1256 CloseDesktop(hDesktop); 1257 } 1258 else 1259 AssertFailed(); 1260 } 1261 else 1262 AssertFailed(); 1263 CloseWindowStation(hWinStation0); 1264 } 1265 else 1266 AssertFailed(); 795 1267 } 796 1268 … … 839 1311 HANDLE hTokenUserDesktop = INVALID_HANDLE_VALUE; 840 1312 1313 /* 1314 * If the SERVICE flag is specified, we do something rather ugly to 1315 * make things work at all. We search for a known desktop process 1316 * belonging to the user, grab its token and use it for launching 1317 * the new process. That way the process will have desktop access. 1318 */ 841 1319 if (fFlags & RTPROC_FLAGS_SERVICE) 842 1320 { … … 883 1361 /* else: !RTPROC_FLAGS_SERVICE: Nothing to do here right now. */ 884 1362 885 /** @todo Hmm, this function already is too big! We need to split 886 * it up into several small parts. */ 887 888 /* If we got an error due to account lookup/loading above, don't 889 * continue here. */ 1363 #if 0 1364 /* 1365 * If we make LogonUserW to return an impersonation token, enable this 1366 * to convert it into a primary token. 1367 */ 1368 if (!fFound && detect-impersonation-token) 1369 { 1370 HANDLE hNewToken; 1371 if (DuplicateTokenEx(hTokenLogon, MAXIMUM_ALLOWED, NULL /*SecurityAttribs*/, 1372 SecurityIdentification, TokenPrimary, &hNewToken)) 1373 { 1374 CloseHandle(hTokenLogon); 1375 hTokenLogon = hNewToken; 1376 } 1377 else 1378 AssertMsgFailed(("%d\n", GetLastError())); 1379 } 1380 #endif 1381 890 1382 if (RT_SUCCESS(rc)) 891 1383 { … … 930 1422 if (RT_SUCCESS(rc)) 931 1423 { 1424 HWINSTA hOldWinStation = NULL; 1425 if (!fFound && g_enmWinVer <= kRTWinOSType_NT4) /** @todo test newer versions... */ 1426 rtProcWinStationPrep(hTokenToUse, pStartupInfo, &hOldWinStation); 1427 932 1428 /* 933 1429 * Useful KB articles: … … 959 1455 rc = RTErrConvertFromWin32(dwErr); 960 1456 } 1457 1458 if (hOldWinStation) 1459 SetProcessWindowStation(hOldWinStation); 961 1460 } 962 1461 RTEnvFreeUtf16Block(pwszzBlock); … … 1089 1588 * gets the environment specified by the user profile. 1090 1589 */ 1091 int rc = VINF_SUCCESS;1590 int rc; 1092 1591 PRTUTF16 pwszzBlock = NULL; 1093 1592 … … 1110 1609 else if ( hEnv == RTENV_DEFAULT 1111 1610 && !(fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_SEARCH_PATH))) 1611 { 1112 1612 pwszzBlock = NULL; 1613 rc = VINF_SUCCESS; 1614 } 1113 1615 /* 1114 1616 * Otherwise, we need to get the user profile environment. … … 1121 1623 if (RT_SUCCESS(rc)) 1122 1624 { 1123 /** @todo r=bird: Why didn't we load the environment here? The 1124 * CreateEnvironmentBlock docs indicate that USERPROFILE isn't set 1125 * unless we call LoadUserProfile first. However, experiments here on W10 1126 * shows it isn't really needed though. Weird. 1127 * Update: It works even on W2K. Possible only required for roaming profiles? */ 1128 #if 0 1129 if (fFlags & RTPROC_FLAGS_PROFILE) 1130 { 1131 PROFILEINFOW ProfileInfo; 1132 RT_ZERO(ProfileInfo); 1133 ProfileInfo.dwSize = sizeof(ProfileInfo); 1134 ProfileInfo.lpUserName = pwszUser; 1135 ProfileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */ 1136 1137 if (g_pfnLoadUserProfileW(hToken, &ProfileInfo)) 1625 /* CreateEnvFromToken docs says we should load the profile, though 1626 we haven't observed any difference when not doing it. Maybe it's 1627 only an issue with roaming profiles or something similar... */ 1628 PROFILEINFOW ProfileInfo; 1629 RT_ZERO(ProfileInfo); 1630 ProfileInfo.dwSize = sizeof(ProfileInfo); 1631 ProfileInfo.lpUserName = pwszUser; 1632 ProfileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */ 1633 1634 if (g_pfnLoadUserProfileW(hToken, &ProfileInfo)) 1635 { 1636 /* 1637 * Do what we need to do. Don't keep any temp environment object. 1638 */ 1639 rc = rtProcWinCreateEnvFromToken(hToken, hEnv, fFlags, &hEnvToUse); 1640 if (RT_SUCCESS(rc)) 1138 1641 { 1139 rc = rtProcWinCreateEnvFromToken(hToken, hEnv, fFlags, &hEnvToUse); 1140 1141 if (!g_pfnUnloadUserProfile(hToken, ProfileInfo.hProfile)) 1142 AssertFailed(); 1642 rc = rtProcWinFindExe(fFlags, hEnv, pszExec, ppwszExec); 1643 if (RT_SUCCESS(rc)) 1644 rc = RTEnvQueryUtf16Block(hEnvToUse, &pwszzBlock); 1645 if (hEnvToUse != hEnv) 1646 RTEnvDestroy(hEnvToUse); 1143 1647 } 1144 else 1145 rc = RTErrConvertFromWin32(GetLastError()); 1648 1649 if (!g_pfnUnloadUserProfile(hToken, ProfileInfo.hProfile)) 1650 AssertFailed(); 1146 1651 } 1147 1652 else 1148 #endif 1149 rc = rtProcWinCreateEnvFromToken(hToken, hEnv, fFlags, &hEnvToUse); 1653 rc = RTErrConvertFromWin32(GetLastError()); 1150 1654 CloseHandle(hToken); 1151 1152 /*1153 * Query the environment block and find the executable file,1154 * Then destroy any temp env block.1155 */1156 if (RT_SUCCESS(rc))1157 {1158 rc = rtProcWinFindExe(fFlags, hEnv, pszExec, ppwszExec);1159 if (RT_SUCCESS(rc))1160 rc = RTEnvQueryUtf16Block(hEnvToUse, &pwszzBlock);1161 if (hEnvToUse != hEnv)1162 RTEnvDestroy(hEnvToUse);1163 }1164 1655 } 1165 1656 } 1166 1657 if (RT_SUCCESS(rc)) 1167 1658 { 1659 /* 1660 * Create the process. 1661 */ 1168 1662 Assert(!(dwCreationFlags & CREATE_SUSPENDED)); 1169 1663 bool const fCreatedSuspended = g_enmWinVer < kRTWinOSType_XP; … … 1185 1679 else 1186 1680 { 1187 /* Duplicate standard handles into the child process, we ignore failures here as it's 1188 legal to have bad standard handle values and we cannot dup console I/O handles. */ 1681 /* 1682 * Duplicate standard handles into the child process, we ignore failures here as it's 1683 * legal to have bad standard handle values and we cannot dup console I/O handles.* 1684 */ 1189 1685 PVOID pvDstProcParamCache = NULL; 1190 1686 rtProcWinDupStdHandleIntoChild(pStartupInfo->hStdInput, pProcInfo->hProcess, … … 1195 1691 RT_OFFSETOF(RTL_USER_PROCESS_PARAMETERS, StandardError), &pvDstProcParamCache); 1196 1692 1197 if (ResumeThread(pProcInfo->hThread) == ~(DWORD)0) 1693 if (ResumeThread(pProcInfo->hThread) != ~(DWORD)0) 1694 rc = VINF_SUCCESS; 1695 else 1198 1696 rc = RTErrConvertFromWin32(GetLastError()); 1199 1697 if (RT_FAILURE(rc)) … … 1208 1706 { 1209 1707 DWORD dwErr = GetLastError(); 1210 rc = rtProcWinMapErrorCodes(dwErr);1708 rc = RTErrConvertFromWin32(dwErr); 1211 1709 if (rc == VERR_UNRESOLVED_ERROR) 1212 1710 LogRelFunc(("g_pfnCreateProcessWithLogonW (%p) failed: dwErr=%u (%#x), rc=%Rrc\n", … … 1754 2252 1755 2253 1756 RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, 1757 size_t *pcbUser) 2254 RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser, size_t *pcbUser) 1758 2255 { 1759 2256 AssertReturn( (pszUser && cbUser > 0) 1760 2257 || (!pszUser && !cbUser), VERR_INVALID_PARAMETER); 1761 1762 if (hProcess != RTProcSelf()) 1763 return VERR_NOT_SUPPORTED; 1764 1765 RTUTF16 awszUserName[UNLEN + 1]; 1766 DWORD cchUserName = UNLEN + 1; 1767 1768 if (!GetUserNameW(&awszUserName[0], &cchUserName)) 1769 return RTErrConvertFromWin32(GetLastError()); 1770 1771 char *pszUserName = NULL; 1772 int rc = RTUtf16ToUtf8(awszUserName, &pszUserName); 1773 if (RT_SUCCESS(rc)) 1774 { 1775 size_t cbUserName = strlen(pszUserName) + 1; 1776 1777 if (pcbUser) 1778 *pcbUser = cbUserName; 1779 1780 if (cbUserName > cbUser) 1781 rc = VERR_BUFFER_OVERFLOW; 2258 AssertReturn(pcbUser || pszUser, VERR_INVALID_PARAMETER); 2259 2260 int rc; 2261 if ( hProcess == NIL_RTPROCESS 2262 || hProcess == RTProcSelf()) 2263 { 2264 RTUTF16 wszUsername[UNLEN + 1]; 2265 DWORD cwcUsername = RT_ELEMENTS(wszUsername); 2266 if (GetUserNameW(&wszUsername[0], &cwcUsername)) 2267 { 2268 if (pszUser) 2269 { 2270 rc = RTUtf16ToUtf8Ex(wszUsername, cwcUsername, &pszUser, cbUser, pcbUser); 2271 if (pcbUser) 2272 *pcbUser += 1; 2273 } 2274 else 2275 { 2276 *pcbUser = RTUtf16CalcUtf8Len(wszUsername) + 1; 2277 rc = VERR_BUFFER_OVERFLOW; 2278 } 2279 } 1782 2280 else 1783 { 1784 memcpy(pszUser, pszUserName, cbUserName); 1785 rc = VINF_SUCCESS; 1786 } 1787 1788 RTStrFree(pszUserName); 1789 } 1790 2281 rc = RTErrConvertFromWin32(GetLastError()); 2282 } 2283 else 2284 rc = VERR_NOT_SUPPORTED; 1791 2285 return rc; 1792 2286 } 1793 2287 2288 2289 RTR3DECL(int) RTProcQueryUsernameA(RTPROCESS hProcess, char **ppszUser) 2290 { 2291 AssertPtrReturn(ppszUser, VERR_INVALID_POINTER); 2292 int rc; 2293 if ( hProcess == NIL_RTPROCESS 2294 || hProcess == RTProcSelf()) 2295 { 2296 RTUTF16 wszUsername[UNLEN + 1]; 2297 DWORD cwcUsername = RT_ELEMENTS(wszUsername); 2298 if (GetUserNameW(&wszUsername[0], &cwcUsername)) 2299 rc = RTUtf16ToUtf8(wszUsername, ppszUser); 2300 else 2301 rc = RTErrConvertFromWin32(GetLastError()); 2302 } 2303 else 2304 rc = VERR_NOT_SUPPORTED; 2305 return rc; 2306 } 2307 -
trunk/src/VBox/Runtime/testcase/tstRTProcQueryUsername.cpp
r57358 r57916 35 35 #include <iprt/test.h> 36 36 37 37 38 static void tstRTProcQueryUsername(void) 38 39 { 39 char abUser[1024];40 size_t cbUser;41 char *pszUser = NULL;42 43 40 RTTestISub("Basics"); 44 41 45 memset(abUser, 0, sizeof(abUser)); 42 size_t cbUser; 43 char szUser[1024]; 44 memset(szUser, '-', sizeof(szUser)); 45 46 /* negative stuff that may assert: */ 47 bool fMayPanic = RTAssertSetMayPanic(false); 48 bool fQuiet = RTAssertSetQuiet(true); 49 46 50 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), NULL, 8, &cbUser), VERR_INVALID_PARAMETER); 47 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), abUser, 0, &cbUser), VERR_INVALID_PARAMETER); 48 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), NULL, 0, NULL), VERR_BUFFER_OVERFLOW); 51 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), szUser, 0, &cbUser), VERR_INVALID_PARAMETER); 52 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), NULL, 0, NULL), VERR_INVALID_PARAMETER); 53 RTTESTI_CHECK_RC(RTProcQueryUsernameA(RTProcSelf(), NULL), VERR_INVALID_POINTER); 54 55 RTAssertSetMayPanic(fMayPanic); 56 RTAssertSetQuiet(fQuiet); 57 49 58 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), NULL, 0, &cbUser), VERR_BUFFER_OVERFLOW); 59 memset(szUser, '-', sizeof(szUser)); 60 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), szUser, cbUser - 1, &cbUser), VERR_BUFFER_OVERFLOW); 61 memset(szUser, '-', sizeof(szUser)); 62 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), &cbUser), VINF_SUCCESS); 63 RTTestPrintf(NULL, RTTESTLVL_ALWAYS, "Username: %s\n", szUser); /* */ 50 64 51 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), abUser, sizeof(abUser), &cbUser), VINF_SUCCESS); 52 RTTestPrintf(NULL, RTTESTLVL_ALWAYS, "Username: %s\n", abUser); 53 RTTESTI_CHECK_RC(RTProcQueryUsername(RTProcSelf(), abUser, cbUser - 1, &cbUser), VERR_BUFFER_OVERFLOW); 54 55 RTTESTI_CHECK_RC(RTProcQueryUsernameA(RTProcSelf(), NULL), VERR_INVALID_POINTER); 65 char *pszUser = NULL; 56 66 RTTESTI_CHECK_RC(RTProcQueryUsernameA(RTProcSelf(), &pszUser), VINF_SUCCESS); 57 67 RTTestPrintf(NULL, RTTESTLVL_ALWAYS, "Username: %s\n", pszUser); 68 RTTESTI_CHECK(strcmp(pszUser, szUser) == 0); 58 69 RTStrFree(pszUser); 59 70 } 71 60 72 61 73 int main(int argc, char **argv) -
trunk/src/VBox/Runtime/win/RTErrConvertFromWin32.cpp
r57906 r57916 50 50 case ERROR_TOO_MANY_OPEN_FILES: return VERR_TOO_MANY_OPEN_FILES; 51 51 case ERROR_ACCESS_DENIED: return VERR_ACCESS_DENIED; 52 case ERROR_NOACCESS: return VERR_ ACCESS_DENIED;52 case ERROR_NOACCESS: return VERR_INVALID_POINTER; /* (STATUS_ACCESS_VIOLATION, STATUS_DATATYPE_MISALIGNMENT, STATUS_DATATYPE_MISALIGNMENT_ERROR) */ 53 53 54 54 case ERROR_INVALID_HANDLE:
Note:
See TracChangeset
for help on using the changeset viewer.

