windows-nt/Source/XPSP1/NT/ds/security/ntmarta/newsrc/registry.cxx

1872 lines
69 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1993 - 1994.
//
// File: registry.cxx
//
// Contents: local functions
//
// History: 8/94 davemont Created
//
//----------------------------------------------------------------------------
#include <aclpch.hxx>
#pragma hdrstop
#include <alsup.hxx>
#include <martaevt.h>
//
// Registry generic mapping
//
GENERIC_MAPPING gRegGenMapping = {STANDARD_RIGHTS_READ | 0x1,
STANDARD_RIGHTS_WRITE | 0x2,
STANDARD_RIGHTS_EXECUTE | 0x4,
STANDARD_RIGHTS_REQUIRED | 0x3F};
//+---------------------------------------------------------------------------
//
// Function : GetDesiredAccess
//
// Synopsis : Gets the access required to open object to be able to set or
// get the specified security info.
//
// Arguments: IN [SecurityOpenType] - Flag indicating if the object is to be
// opened to read or write the DACL
//
//----------------------------------------------------------------------------
ACCESS_MASK RegGetDesiredAccess(IN SECURITY_OPEN_TYPE OpenType,
IN SECURITY_INFORMATION SecurityInfo)
{
acDebugOut((DEB_TRACE_ACC, "in GetDesiredAccess \n"));
ACCESS_MASK DesiredAccess = 0;
if ( (SecurityInfo & OWNER_SECURITY_INFORMATION) ||
(SecurityInfo & GROUP_SECURITY_INFORMATION) )
{
switch (OpenType)
{
case READ_ACCESS_RIGHTS:
DesiredAccess |= READ_CONTROL;
break;
case WRITE_ACCESS_RIGHTS:
DesiredAccess |= WRITE_OWNER;
break;
case MODIFY_ACCESS_RIGHTS:
DesiredAccess |= READ_CONTROL | WRITE_OWNER;
break;
}
}
if (SecurityInfo & DACL_SECURITY_INFORMATION)
{
switch (OpenType)
{
case READ_ACCESS_RIGHTS:
DesiredAccess |= READ_CONTROL;
break;
case WRITE_ACCESS_RIGHTS:
DesiredAccess |= WRITE_DAC;
break;
case MODIFY_ACCESS_RIGHTS:
DesiredAccess |= READ_CONTROL | WRITE_DAC;
break;
}
}
if (SecurityInfo & SACL_SECURITY_INFORMATION)
{
DesiredAccess |= READ_CONTROL | ACCESS_SYSTEM_SECURITY;
}
acDebugOut((DEB_TRACE_ACC, "out RegGetDesiredAccess: %lu\n", DesiredAccess));
return (DesiredAccess);
}
//+---------------------------------------------------------------------------
//
// Function: OpenRegistryObject
//
// Synopsis: Opens the specified registry object
//
// Arguments: [IN pwszRegistry] -- The name of the registry key
// to open
// [IN AccessMask] -- Flags indicating if the object
// is to be opened to read or write
// the DACL
// [OUT pHandle] -- Where the open handle is
// returned
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed
// ERROR_INVALID_PARAMETER -- A bad name was given
//
//----------------------------------------------------------------------------
DWORD
OpenRegistryObject(IN LPWSTR pwszRegistry,
IN ACCESS_MASK AccessMask,
OUT PHANDLE pHandle)
{
acDebugOut((DEB_TRACE, "in OpenRegistryObject\n"));
DWORD dwErr;
HKEY hBase;
if(pwszRegistry != NULL)
{
WCHAR wszName[MAX_PATH + 1];
PWSTR pwszName;
//
// save the object since we must crack it to go to remote machines
//
dwErr = AccGetBufferOfSizeW(pwszRegistry,
wszName,
&pwszName);
if(dwErr == ERROR_SUCCESS)
{
PWSTR pwszRemaining, pwszMachine;
//
// Separate the names
//
dwErr = ParseName(pwszName,
&pwszMachine,
&pwszRemaining);
if(dwErr == ERROR_SUCCESS)
{
PWSTR pwszKey = NULL;
//
// look for the key names localization required.
//
if (pwszRemaining != NULL)
{
PWSTR pwszBase = pwszRemaining;
pwszKey = wcschr(pwszRemaining, L'\\');
if(pwszKey != NULL)
{
*pwszKey = L'\0';
pwszKey++;
}
//
// Now, figure out what our base key will be
//
if(_wcsicmp(pwszBase, L"MACHINE") == 0)
{
hBase = HKEY_LOCAL_MACHINE;
}
else if(_wcsicmp(pwszBase, L"USERS") == 0 ||
_wcsicmp(pwszBase, L"USER") == 0 )
{
hBase = HKEY_USERS;
}
//
// The next three are valid only for the local machine
//
else if(pwszMachine == NULL &&
_wcsicmp(pwszBase, L"CLASSES_ROOT") == 0)
{
hBase = HKEY_CLASSES_ROOT;
}
else if(pwszMachine == NULL &&
_wcsicmp(pwszBase,L"CURRENT_USER") == 0)
{
hBase = HKEY_CURRENT_USER;
}
else if(pwszMachine == NULL &&
_wcsicmp(pwszBase, L"CONFIG") == 0)
{
hBase = HKEY_CURRENT_CONFIG;
}
else
{
dwErr = ERROR_INVALID_PARAMETER;
}
}
else
{
dwErr = ERROR_INVALID_PARAMETER;
}
if(dwErr == ERROR_SUCCESS)
{
//
// if it is a remote name, connect to that registry
//
if(pwszMachine != NULL)
{
HKEY hMach = hBase;
dwErr = RegConnectRegistry(pwszMachine,
hMach,
&hBase);
}
//
// Now, open the key
//
if(dwErr == ERROR_SUCCESS)
{
dwErr = RegOpenKeyEx(hBase,
pwszKey,
0,
AccessMask,
(PHKEY)pHandle);
if(pwszMachine != NULL)
{
RegCloseKey(hBase);
}
}
}
}
AccFreeBufferOfSizeW(wszName, pwszName);
}
}
else
{
dwErr = ERROR_INVALID_PARAMETER;
}
acDebugOut((DEB_TRACE, "Out OpenRegistryObject: %lu\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: ReadRegistryPropertyRights
//
// Synopsis: Gets the specified security info for the specified registry
// object
//
// Arguments: [IN pwszRegistry] -- The reg key to get the rights
// for
// [IN pRightsList] -- SecurityInfo to read based
// on properties
// [IN cRights] -- Number of items in rights list
// [IN AccessList] -- Access List to fill in
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_INVALID_PARAMETER -- A bad property was encountered
// ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed
//
//----------------------------------------------------------------------------
DWORD
ReadRegistryPropertyRights(IN LPWSTR pwszRegistry,
IN PACTRL_RIGHTS_INFO pRightsList,
IN ULONG cRights,
IN CAccessList& AccessList)
{
acDebugOut((DEB_TRACE, "in ReadRegistryPropertyRights\n"));
HANDLE hReg;
DWORD dwErr;
//
// Currently, there are only registry object properties
//
ASSERT(cRights == 1 && pRightsList[0].pwszProperty == NULL);
if(cRights != 1 || pRightsList[0].pwszProperty != NULL)
{
return(ERROR_INVALID_PARAMETER);
}
//
// Set the lookup server name
//
dwErr = SetAccessListLookupServer( pwszRegistry,
AccessList );
//
// Open the registry key
//
if(dwErr == ERROR_SUCCESS)
{
dwErr = OpenRegistryObject(pwszRegistry,
RegGetDesiredAccess(READ_ACCESS_RIGHTS,
pRightsList[0].SeInfo),
&hReg);
}
if(dwErr == ERROR_SUCCESS)
{
PSECURITY_DESCRIPTOR pSD;
dwErr = ReadRegistrySecurityInfo(hReg,
pRightsList[0].SeInfo,
&pSD);
//
// If that worked, we'll have to get the parent SD, if it exists,
// and see if we can determine the inheritance on our current object
//
if(dwErr == ERROR_SUCCESS)
{
if((pRightsList[0].SeInfo & ~(OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) != 0 &&
!FLAG_ON(((PISECURITY_DESCRIPTOR)pSD)->Control,
SE_SACL_AUTO_INHERITED |
SE_DACL_AUTO_INHERITED))
{
//
// Ok, it's downlevel, so get the parent SD...
//
PSECURITY_DESCRIPTOR pParentSD;
dwErr = GetRegistryParentRights(pwszRegistry,
pRightsList[0].SeInfo,
&pParentSD);
//
// Also, the routine to convert from nt4 to nt5 security
// descriptor requires that we have the owner and group,
// so we may have to reread the child SD if we don't have
// that info
//
if(dwErr == ERROR_SUCCESS && (!FLAG_ON(pRightsList[0].SeInfo,
OWNER_SECURITY_INFORMATION) ||
!FLAG_ON(pRightsList[0].SeInfo,
GROUP_SECURITY_INFORMATION)))
{
AccFree(pSD);
pSD = NULL;
dwErr = ReadRegistrySecurityInfo(hReg,
pRightsList[0].SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
&pSD);
}
//
// A NULL parent SD means this object has no parent!
//
if(dwErr == ERROR_SUCCESS && pParentSD != NULL)
{
PSECURITY_DESCRIPTOR pNewSD;
dwErr = ConvertToAutoInheritSD(pParentSD,
pSD,
TRUE,
&gRegGenMapping,
&pNewSD);
if(dwErr == ERROR_SUCCESS)
{
dwErr = AccessList.AddSD(pNewSD,
pRightsList[0].SeInfo,
pRightsList[0].pwszProperty);
DestroyPrivateObjectSecurity(&pNewSD);
}
AccFree(pParentSD);
}
}
else
{
//
// Simply add the SD to our list
//
dwErr = AccessList.AddSD(pSD,
pRightsList[0].SeInfo,
pRightsList[0].pwszProperty);
}
//
// Make sure to free the security descriptor...
//
AccFree(pSD);
}
RegCloseKey((HKEY)hReg);
}
acDebugOut((DEB_TRACE, "Out ReadRegistryPropertyRights: %lu\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: ReadRegistryRights
//
// Synopsis: Gets the specified security info for the specified registry
// object
//
// Arguments: [IN hRegistry] -- Reg handle to get the rights
// for
// [IN pRightsList] -- SecurityInfo to read based
// on properties
// [IN cRights] -- Number of items in rights list
// [IN AccessList] -- Access List to fill in
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_INVALID_PARAMETER -- A bad property was encountered
// ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed
//
//----------------------------------------------------------------------------
DWORD
ReadRegistryRights(IN HANDLE hRegistry,
IN PACTRL_RIGHTS_INFO pRightsList,
IN ULONG cRights,
IN CAccessList& AccessList)
{
acDebugOut((DEB_TRACE, "in ReadRegistryRights\n"));
DWORD dwErr;
//
// Currently, there are only registry object properties
//
ASSERT(cRights == 1 && pRightsList[0].pwszProperty == NULL);
if(cRights != 1 || pRightsList[0].pwszProperty != NULL)
{
return(ERROR_INVALID_PARAMETER);
}
PSECURITY_DESCRIPTOR pSD = NULL;
dwErr = ReadRegistrySecurityInfo(hRegistry,
pRightsList[0].SeInfo,
&pSD);
if((dwErr != ERROR_SUCCESS) || (pSD == NULL))
{
return(dwErr);
}
//
// Take a look at it... If it's a downlevel object, let's reread it as an uplevel, if
// possible
//
if(!FLAG_ON(((PISECURITY_DESCRIPTOR)pSD)->Control,
SE_SACL_AUTO_INHERITED |
SE_DACL_AUTO_INHERITED))
{
PWSTR pwszRegPath = NULL;
dwErr = ConvertRegHandleToName((HKEY)hRegistry,
&pwszRegPath);
if(dwErr != ERROR_SUCCESS)
{
if(dwErr == ERROR_INVALID_HANDLE)
{
//
// It's remote, so add it as is...
//
dwErr = AccessList.AddSD(pSD,
pRightsList->SeInfo,
pRightsList->pwszProperty);
}
}
else
{
dwErr = ReadRegistryPropertyRights(pwszRegPath,
pRightsList,
cRights,
AccessList);
AccFree(pwszRegPath);
}
}
else
{
//
// It's already uplevel, so add it as is...
//
dwErr = AccessList.AddSD(pSD,
pRightsList->SeInfo,
pRightsList->pwszProperty);
}
AccFree(pSD);
acDebugOut((DEB_TRACE, "Out ReadRegistryRights: %lu\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: ReadRegistrySecurityInfo
//
// Synopsis: Reads the specified security info for the handle's registry
// key object
//
// Arguments: [IN hRegistry] -- The handle to the object to
// get the rights for
// [IN SeInfo] -- SecurityInfo to read based
// [OUT ppOwner] -- The owner sid
// [OUT ppGroup] -- The group sid
// [OUT pDAcl] -- The DACL
// [OUT pSAcl] -- The SACL
// [OUT pSD] -- The security descriptor itself
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed
//
//----------------------------------------------------------------------------
DWORD
ReadRegistrySecurityInfo(IN HANDLE hRegistry,
IN SECURITY_INFORMATION SeInfo,
OUT PSECURITY_DESCRIPTOR *ppSD)
{
acDebugOut((DEB_TRACE, "in ReadRegistrySecurityInfo \n"));
ULONG cSize = 0;
DWORD dwErr;
//
// First, get the size we need
//
dwErr = RegGetKeySecurity((HKEY)hRegistry,
SeInfo,
*ppSD,
&cSize);
if(dwErr == ERROR_INSUFFICIENT_BUFFER)
{
dwErr = ERROR_SUCCESS;
*ppSD = (PISECURITY_DESCRIPTOR)AccAlloc(cSize);
if(*ppSD == NULL)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
}
else
{
dwErr = RegGetKeySecurity((HKEY)hRegistry,
SeInfo,
*ppSD,
&cSize);
if(dwErr == ERROR_SUCCESS &&
FLAG_ON(((SECURITY_DESCRIPTOR *)*ppSD)->Control,SE_SELF_RELATIVE))
{
PSECURITY_DESCRIPTOR pAbs;
dwErr = MakeSDAbsolute(*ppSD,
SeInfo,
&pAbs);
if(dwErr == ERROR_SUCCESS)
{
AccFree(*ppSD);
*ppSD = pAbs;
}
}
}
}
else
{
ASSERT(dwErr != ERROR_INSUFFICIENT_BUFFER);
}
acDebugOut((DEB_TRACE, "Out ReadRegistrySecurityInfo: %lu\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: GetRegistryParentRights
//
// Synopsis: Determines who the parent is, and gets the access rights
// for it. It is used to aid in determining what the approriate
// inheritance bits are.
//
// This operation does not make sense for kernel objects
//
// Arguments: [IN pwszRegistry] -- The reg path to get the parent
// for
// [IN SeInfo] -- The security information to do
// the read for
// [OUT ppDAcl] -- Where the DACL is returned
// [OUT ppSAcl] -- Where the SACL is returned
// [OUT ppSD] -- Where the Security Descriptor
// is returned
//
// Returns: ERROR_SUCCESS -- Success
//
//
//----------------------------------------------------------------------------
DWORD
GetRegistryParentRights(IN LPWSTR pwszRegistry,
IN SECURITY_INFORMATION SeInfo,
OUT PSECURITY_DESCRIPTOR *ppSD)
{
DWORD dwErr = ERROR_SUCCESS;
//
// Basically, we'll figure out who our parent is, and get their info
//
PWSTR pwszLastComp = wcsrchr(pwszRegistry, L'\\');
if(pwszLastComp == NULL)
{
//
// Ok, we must be at the root, so we won't have any inheritance
//
//
// Return success after nulling out SD.
//
*ppSD = NULL;
}
else
{
//
// We'll shorten our path, and then get the info
//
*pwszLastComp = L'\0';
HANDLE hReg;
//
// Don't want owner or group
//
SeInfo &= ~(OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION);
dwErr = OpenRegistryObject(pwszRegistry,
RegGetDesiredAccess(READ_ACCESS_RIGHTS,SeInfo),
&hReg);
if(dwErr == ERROR_SUCCESS)
{
dwErr = ReadRegistrySecurityInfo(hReg,
SeInfo,
ppSD);
RegCloseKey((HKEY)hReg);
}
*pwszLastComp = L'\\';
}
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: SetRegistrySecurityInfo
//
// Synopsis: Sets the specified security info on the specified registry
// object
//
// Arguments: [IN hService] -- The handle of the object
// [IN SeInfo] -- Flag indicating what security
// info to set
// [IN pwszProperty] -- The property on the object to
// set
// For kernel objects, this MBZ
// [IN pSD] -- The security descriptor to set
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_INVALID_PARAMETER -- A bad property was given
//
//----------------------------------------------------------------------------
DWORD
SetRegistrySecurityInfo(IN HANDLE hRegistry,
IN SECURITY_INFORMATION SeInfo,
IN PWSTR pwszProperty,
IN PSECURITY_DESCRIPTOR pSD)
{
acDebugOut((DEB_TRACE, "in SetNamedRegistrySecurityInfo\n"));
DWORD dwErr;
//
// Registry keys don't have properties
//
if(pwszProperty != NULL)
{
dwErr = ERROR_INVALID_PARAMETER;
}
else
{
//
// Marta only writes uplevel security descriptors.
//
// The caller of SetRegistrySecurityInfo will call with SE_xACL_AUTO_INHERITED off in those
// cases that it wants the underlying registry to do auto inheritance.
// The caller of SetRegistrySecurityInfo will call with SE_xACL_AUTO_INHERITED on in those
// cases that it wants the underlying registry to simply store the bits.
//
// In the later case, the OS uses the SE_xACL_AUTO_INHERIT_REQ bit as a flag indicating
// that it is OK to preserve SE_xACL_AUTO_INHERITED bit.
//
if(FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION)) {
((PISECURITY_DESCRIPTOR)pSD)->Control |= SE_DACL_AUTO_INHERIT_REQ;
}
if(FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION)) {
((PISECURITY_DESCRIPTOR)pSD)->Control |= SE_SACL_AUTO_INHERIT_REQ;
}
dwErr = RegSetKeySecurity((HKEY)hRegistry,
SeInfo,
pSD);
}
acDebugOut((DEB_TRACE, "Out SetRegistrySecurityInfo: %lu\n", dwErr));
return(dwErr);
}
#define CLEANUP_ON_INTERRUPT(pstopflag) \
if(*pstopflag != 0) \
{ \
goto RegCleanup; \
}
//+---------------------------------------------------------------------------
//
// Function: SetAndPropagateRegistryPropertyRights
//
// Synopsis: Sets the access on the given registry path and propagates
// it as necessary
//
// Arguments: [IN pwszRegistry] -- The path to set and propagate
// [IN pwszProperty] -- The registry property to
// operate upon
// [IN RootAccList] -- The CAccessList class that has
// the security descriptor/info
// [IN pfStopFlag] -- Address of the stop flag
// to be monitored
// [IN pcProcessed] -- count of processed items to
// be incremented.
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_INVALID_PARAMETER -- A bad paramter was given
//
//----------------------------------------------------------------------------
DWORD
SetAndPropagateRegistryPropertyRights(IN PWSTR pwszRegistry,
IN PWSTR pwszProperty,
IN CAccessList& RootAccList,
IN PULONG pfStopFlag,
IN PULONG pcProcessed)
{
acDebugOut((DEB_TRACE, "in SetAndPropagateRegistryPropertyRights\n"));
DWORD dwErr = ERROR_SUCCESS;
//
// First, get our security descriptor and sec info
//
HKEY hReg = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
PSECURITY_DESCRIPTOR pParentSD = NULL;
SECURITY_INFORMATION SeInfo = 0;
dwErr = RootAccList.BuildSDForAccessList(&pSD,
&SeInfo,
ACCLIST_SD_ABSOK);
if(dwErr == ERROR_SUCCESS)
{
//
// Next, open the registry
//
dwErr = OpenRegistryObject(pwszRegistry,
RegGetDesiredAccess(MODIFY_ACCESS_RIGHTS,
SeInfo) |
KEY_ENUMERATE_SUB_KEYS |
KEY_QUERY_VALUE,
(PHANDLE)&hReg);
if(dwErr == ERROR_SUCCESS)
{
//
// Next, get our parent security descriptor
//
//
// If we are only setting the owner or group, we don't need to get the parent
//
if (FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION) ||
FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION) ) {
dwErr = GetRegistryParentRights(pwszRegistry,
SeInfo,
&pParentSD);
}
if(dwErr == ERROR_SUCCESS)
{
//
// Make the call
//
dwErr = SetAndPropRegRights(hReg,
pwszRegistry,
SeInfo,
pParentSD,
pSD,
pfStopFlag,
pcProcessed);
}
}
}
//
// Clean up
//
if(hReg != NULL)
{
RegCloseKey(hReg);
}
AccFree(pParentSD);
acDebugOut((DEB_TRACE,
"Out SetAndPropagateRegistryPropertyRights: %ld\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: SetAndPropagateRegistryPropertyRightsByHandle
//
// Synopsis: Same as above, but assumes the registry key has already
// been opened
//
// Arguments: [IN hReg] -- The registry key to use
// [IN RootAccList] -- The CAccessList class that has
// the security descriptor/info
// [IN pfStopFlag] -- Address of the stop flag
// to be monitored
// [IN pcProcessed] -- count of processed items to
// be incremented.
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_INVALID_PARAMETER -- A bad paramter was given
//
//----------------------------------------------------------------------------
DWORD
SetAndPropagateRegistryPropertyRightsByHandle(IN HKEY hReg,
IN CAccessList& RootAccList,
IN PULONG pfStopFlag,
IN PULONG pcProcessed)
{
acDebugOut((DEB_TRACE, "in SetAndPropagateRegistryPropertyRightsByHandle\n"));
DWORD dwErr = ERROR_SUCCESS;
PSECURITY_DESCRIPTOR pParentSD = NULL;
HANDLE hObject = NULL;
BOOL fUplevelAcl = TRUE;
PWSTR pwszRegPath = NULL;
//
// First, get our security descriptor and sec info
//
PSECURITY_DESCRIPTOR pSD = NULL;
SECURITY_INFORMATION SeInfo = 0;
dwErr = RootAccList.BuildSDForAccessList(&pSD,
&SeInfo,
ACCLIST_SD_ABSOK);
if(dwErr == ERROR_SUCCESS)
{
//
// If we are only setting the owner or group, we don't need to get the parent
//
if (FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION) ||
FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION) ) {
dwErr = ConvertRegHandleToName(hReg,
&pwszRegPath);
if((dwErr != ERROR_SUCCESS) || (pwszRegPath == NULL))
{
if(dwErr == ERROR_INVALID_HANDLE)
{
dwErr = ERROR_SUCCESS;
}
}
else
{
dwErr = GetRegistryParentRights(pwszRegPath,
SeInfo,
&pParentSD);
}
}
if(dwErr == ERROR_SUCCESS)
{
//
// Make the call
//
dwErr = SetAndPropRegRights(hReg,
NULL,
SeInfo,
pParentSD,
pSD,
pfStopFlag,
pcProcessed);
if(dwErr == ERROR_ACCESS_DENIED)
{
//
// See if we can reopen the path adding in readcontrol, and try it all again
//
if(pwszRegPath == NULL)
{
dwErr = ConvertRegHandleToName(hReg,
&pwszRegPath);
}
if(pwszRegPath != NULL)
{
dwErr = SetAndPropagateRegistryPropertyRights(pwszRegPath,
NULL,
RootAccList,
pfStopFlag,
pcProcessed);
}
}
}
}
//
// Clean up
//
AccFree(pwszRegPath);
AccFree(pParentSD);
acDebugOut((DEB_TRACE,
"Out SetAndPropagateRegistryPropertyRightsByHandle: %ld\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: PropagateRegRightsDeep, recursive
//
// Synopsis: Does a deep propagation of the access. At the same time, it
// will update NT4 acls to NT5 acls. This function is only
// called on downlevel registries, so the update will always
// happen (where appropriate). The algorithm is:
// - Read the current security descriptor from the object
// - If it's a downlevel acl, update it using the OLD
// parent security descriptor (to set any inheritied aces)
// - Update the security descriptor using the NEW parent
// security descriptor.
// - Repeat for its children. (This is necessar, since there
// could have been unmarked inheritance off of the old
// security descriptor)
//
// Arguments: [IN pOldParentSD] -- The previous parent SD (before
// the current parent SD was
// stamped on the object)
// [IN pParentSD] -- The current parent sd
// [IN SeInfo] -- What is being written
// [IN hParent] -- Opened parent registry key
// [IN pcProcessed] -- Where the number processed is
// returned.
// [IN pfStopFlag] -- Stop flag to monitor
// [IN fProtectedFlag] -- Determines whether the acls are already
// protected
// [IN hProcessToken] -- Handle to the process token
// [IN LogList] -- List of keys to which propagation failed
//
// Returns: ERROR_SUCCESS -- Success
// ERROR_INVALID_PARAMETER -- A bad paramter was given
//
//----------------------------------------------------------------------------
DWORD
PropagateRegRightsDeep(IN PSECURITY_DESCRIPTOR pOldParentSD,
IN PSECURITY_DESCRIPTOR pParentSD,
IN SECURITY_INFORMATION SeInfo,
IN HKEY hParent,
IN PULONG pcProcessed,
IN PULONG pfStopFlag,
IN ULONG fProtectedFlag,
IN HANDLE hProcessToken,
IN OUT CSList& LogList)
{
acDebugOut((DEB_TRACE, "in PropagteRegRightsDeep\n"));
DWORD dwErr = ERROR_SUCCESS;
SECURITY_DESCRIPTOR *pChildSD = NULL;
PSECURITY_DESCRIPTOR pNewSD = NULL;
BOOL fUpdateChild = FALSE; // Write out the child?
BOOL fAccFreeChild = TRUE; // How to free the child
//
// Check to see if we've reached full protection saturation
//
if(fProtectedFlag == (SE_DACL_PROTECTED | SE_SACL_PROTECTED))
{
acDebugOut((DEB_TRACE_PROP, "Parent is fully or effectively protected\n"));
return(ERROR_SUCCESS);
}
HKEY hChild = NULL;
ULONG cSubKeys;
dwErr = RegQueryInfoKey(hParent,
NULL,
NULL,
NULL,
&cSubKeys,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL);
CLEANUP_ON_INTERRUPT(pfStopFlag);
if(dwErr == ERROR_SUCCESS && cSubKeys != 0)
{
WCHAR wszBuff[MAX_PATH + 1];
ULONG iIndex = 0;
ULONG cSize;
FILETIME WriteTime;
while(dwErr == ERROR_SUCCESS)
{
cSize = MAX_PATH + 1;
dwErr = RegEnumKeyEx(hParent,
iIndex,
wszBuff,
&cSize,
NULL,
NULL,
NULL,
&WriteTime);
if(dwErr == ERROR_NO_MORE_ITEMS)
{
dwErr = ERROR_SUCCESS;
break;
}
acDebugOut((DEB_TRACE_PROP,"Propagating to %ws\n", wszBuff));
CLEANUP_ON_INTERRUPT(pfStopFlag);
//
// Now, determine if we need to propagate or not...
//
if(dwErr == ERROR_SUCCESS)
{
ULONG cSDLen = 0;
BOOL fWriteSD = FALSE;
dwErr = RegOpenKeyEx(hParent,
wszBuff,
0,
RegGetDesiredAccess(MODIFY_ACCESS_RIGHTS,
SeInfo) |
KEY_ENUMERATE_SUB_KEYS |
KEY_QUERY_VALUE,
&hChild);
if(dwErr == ERROR_SUCCESS)
{
//
// Get our number of children
//
dwErr = RegQueryInfoKey(hChild,
NULL,
NULL,
NULL,
&cSubKeys,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL);
if ( dwErr == ERROR_INSUFFICIENT_BUFFER ) {
acDebugOut((DEB_ERROR,"RegQueryInfoKey failure on %ws\n", wszBuff));
}
if(dwErr == ERROR_SUCCESS)
{
CLEANUP_ON_INTERRUPT(pfStopFlag);
//
// Read the current security descriptor
//
dwErr = ReadRegistrySecurityInfo(hChild,
SeInfo,
(PSECURITY_DESCRIPTOR *)&pChildSD);
CLEANUP_ON_INTERRUPT(pfStopFlag);
if(dwErr == ERROR_SUCCESS &&
!(FLAG_ON(pChildSD->Control,
SE_DACL_AUTO_INHERITED |
SE_SACL_AUTO_INHERITED)))
{
//
// Before we convert this, we may need to reread the SD... if
// we don't have owner and group
//
if(!FLAG_ON(SeInfo, OWNER_SECURITY_INFORMATION) ||
!FLAG_ON(SeInfo, GROUP_SECURITY_INFORMATION))
{
AccFree(pChildSD);
pChildSD = NULL;
dwErr = ReadRegistrySecurityInfo(
hChild,
SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
(PSECURITY_DESCRIPTOR *)&pChildSD);
if(dwErr == ERROR_ACCESS_DENIED)
{
RegCloseKey(hChild);
dwErr = RegOpenKeyEx(hParent,
wszBuff,
0,
RegGetDesiredAccess(MODIFY_ACCESS_RIGHTS,
SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION) |
KEY_ENUMERATE_SUB_KEYS |
KEY_QUERY_VALUE,
&hChild);
if(dwErr == ERROR_SUCCESS)
{
dwErr = ReadRegistrySecurityInfo(
hChild,
SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
(PSECURITY_DESCRIPTOR *)&pChildSD);
}
}
}
if(dwErr == ERROR_SUCCESS)
{
dwErr = ConvertToAutoInheritSD(pOldParentSD,
pChildSD,
TRUE,
&gRegGenMapping,
&pNewSD);
AccFree(pChildSD);
if(dwErr == ERROR_SUCCESS)
{
pChildSD = (SECURITY_DESCRIPTOR *)pNewSD;
fAccFreeChild = FALSE;
pNewSD = NULL;
}
}
}
//
// Now, compute the new security descriptor
if(dwErr == ERROR_SUCCESS)
{
DebugDumpSD("CPOS ParentSD", pParentSD);
DebugDumpSD("CPOS CreatorSD", pChildSD);
if(CreatePrivateObjectSecurityEx(pParentSD,
pChildSD,
&pNewSD,
NULL,
TRUE,
SEF_DACL_AUTO_INHERIT |
SEF_SACL_AUTO_INHERIT |
SEF_AVOID_OWNER_CHECK |
SEF_AVOID_PRIVILEGE_CHECK,
hProcessToken,
&gRegGenMapping) == FALSE)
{
dwErr = GetLastError();
}
}
#ifdef DBG
else
{
DebugDumpSD("CPOS NewChild", pNewSD);
}
#endif
if(dwErr == ERROR_SUCCESS)
{
//
// If the resultant child is protected, don't bother propagating
// down.
//
if(FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION))
{
if(DACL_PROTECTED(pNewSD))
{
fProtectedFlag |= SE_DACL_PROTECTED;
}
}
if(FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION))
{
if(SACL_PROTECTED(pNewSD))
{
fProtectedFlag |= SE_SACL_PROTECTED;
}
}
if(FLAG_ON( fProtectedFlag, (SE_DACL_PROTECTED | SE_SACL_PROTECTED)))
{
cSubKeys = 0;
dwErr = InsertPropagationFailureEntry(LogList,
0,
fProtectedFlag,
wszBuff);
}
//
// If we haven't changed the acl, security descriptor, then
// we can also quit
//
if(EqualSecurityDescriptors(pNewSD, pChildSD))
{
cSubKeys = 0;
}
}
}
//
// Now, if it's a directory, call ourselves
//
if(dwErr == ERROR_SUCCESS && cSubKeys != 0)
{
dwErr = PropagateRegRightsDeep(pChildSD,
pNewSD,
SeInfo,
hChild,
pcProcessed,
pfStopFlag,
fProtectedFlag,
hProcessToken,
LogList);
if(dwErr == ERROR_ACCESS_DENIED)
{
dwErr = InsertPropagationFailureEntry(LogList,
dwErr,
0,
wszBuff);
}
}
//
// Free the old child, since we won't need it anymore
//
if(fAccFreeChild == TRUE)
{
AccFree(pChildSD);
}
else
{
DestroyPrivateObjectSecurity((PSECURITY_DESCRIPTOR *)
&pChildSD);
}
pChildSD = NULL;
}
}
acDebugOut((DEB_TRACE_PROP,
"Processed %ws: %lu\n",
wszBuff,
dwErr));
//
// Finally, set the new security
//
if(dwErr == ERROR_SUCCESS)
{
//
// Now, we'll simply stamp it on the object
//
dwErr = SetRegistrySecurityInfo(hChild,
SeInfo,
NULL,
pNewSD);
(*pcProcessed)++;
}
DestroyPrivateObjectSecurity(&pNewSD);
pNewSD = NULL;
CLEANUP_ON_INTERRUPT(pfStopFlag);
iIndex++;
}
}
if(dwErr == ERROR_NO_MORE_FILES)
{
dwErr = ERROR_SUCCESS;
}
RegCleanup:
if(hChild != NULL)
{
RegCloseKey(hChild);
}
if(pNewSD != NULL)
{
DestroyPrivateObjectSecurity(&pNewSD);
}
acDebugOut((DEB_TRACE,
"Out PropagteRegRightsDeep: %ld\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: SetAndPropRegRights
//
// Synopsis: Sets the access on the given registry path and propagates
// it as necessary
//
// Arguments: [IN hReg] -- Handle to the reg. object to set
// [IN pwszPath] -- Registry path referred to by hReg,
// if known
// [IN SeInfo] -- Security information to set
// [IN pParentSD] -- Security descriptor of the parent
// [IN pSD] -- SD to set
// [IN pfStopFlag] -- Address of the stop flag
// to be monitored
// [IN pcProcessed] -- count of processed items to
// be incremented.
//
// Returns: ERROR_SUCCESS -- Success
//
//----------------------------------------------------------------------------
DWORD
SetAndPropRegRights(IN HKEY hReg,
IN PWSTR pwszPath,
IN SECURITY_INFORMATION SeInfo,
IN PSECURITY_DESCRIPTOR pParentSD,
IN PSECURITY_DESCRIPTOR pSD,
IN PULONG pfStopFlag,
IN PULONG pcProcessed)
{
acDebugOut((DEB_TRACE, "in SetAndPropRegRights\n"));
DWORD dwErr = ERROR_SUCCESS;
PSECURITY_DESCRIPTOR pOldObjSD = NULL;
PSECURITY_DESCRIPTOR pUpdatedSD = NULL;
PSECURITY_DESCRIPTOR pVerifySD = NULL;
BOOL fManualProp = FALSE;
ULONG fProtected = 0;
ULONG cSubKeys;
HANDLE hProcessToken = NULL;
PSID pOwner = NULL, pGroup = NULL;
CSList FailureLogList(FreePropagationFailureListEntry);
//
// Ok, read the existing security
//
dwErr = ReadRegistrySecurityInfo(hReg,
SeInfo,
&pOldObjSD);
//
// Now, we'll write out the current, and then read it back and make sure
// that it's properly updated
//
if(dwErr == ERROR_SUCCESS &&
FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION) )
{
CLEANUP_ON_INTERRUPT(pfStopFlag);
dwErr = SetRegistrySecurityInfo(hReg,
SeInfo,
NULL,
pSD);
if(dwErr == ERROR_SUCCESS)
{
(*pcProcessed)++;
CLEANUP_ON_INTERRUPT(pfStopFlag);
dwErr = ReadRegistrySecurityInfo(hReg,
SeInfo,
&pVerifySD);
if(dwErr == ERROR_SUCCESS)
{
//
// Check to see if this was done uplevel...
//
PISECURITY_DESCRIPTOR pISD = (PISECURITY_DESCRIPTOR)pVerifySD;
if(!(FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION) &&
FLAG_ON(pISD->Control, SE_DACL_AUTO_INHERITED)) &&
!(FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION) &&
FLAG_ON(pISD->Control, SE_SACL_AUTO_INHERITED)))
{
//
// It's not uplevel, so we'll turn the AutoInherit
// flags on, rewrite it, and do our own propagation,
// only if this is a container and we're setting the
// dacl or sacl
//
if(FLAG_ON(SeInfo,
(DACL_SECURITY_INFORMATION |
SACL_SECURITY_INFORMATION)))
{
fManualProp = TRUE;
}
if(FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION))
{
pISD->Control |= SE_DACL_AUTO_INHERITED;
}
if(FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION))
{
pISD->Control |= SE_SACL_AUTO_INHERITED;
}
//
// Go ahead and upgrade it to autoinherit
//
if(!FLAG_ON(SeInfo, OWNER_SECURITY_INFORMATION) ||
!FLAG_ON(SeInfo, GROUP_SECURITY_INFORMATION))
{
//
// Need to reread it to get the owner and group
//
AccFree(pVerifySD);
dwErr = ReadRegistrySecurityInfo(hReg,
SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
&pVerifySD);
//
// If we failed to read it because we didn't originally have permissions
// and we have the path, we'll try to reopen the handle with the
// proper rights
//
if(dwErr == ERROR_ACCESS_DENIED && pwszPath != NULL)
{
HKEY hReg2;
dwErr = OpenRegistryObject(pwszPath,
RegGetDesiredAccess(READ_ACCESS_RIGHTS,
SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION),
(PHANDLE)&hReg2);
if(dwErr == ERROR_SUCCESS)
{
dwErr = ReadRegistrySecurityInfo(hReg2,
SeInfo |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
&pVerifySD);
RegCloseKey(hReg2);
}
}
//
// Set our owner/group in the old security descriptor
//
if(dwErr == ERROR_SUCCESS)
{
BOOL fDefaulted;
if(!FLAG_ON(SeInfo, OWNER_SECURITY_INFORMATION))
{
if(!GetSecurityDescriptorOwner(pVerifySD, &pOwner, &fDefaulted))
{
dwErr = GetLastError();
}
}
if(!FLAG_ON(SeInfo, GROUP_SECURITY_INFORMATION))
{
if(!GetSecurityDescriptorGroup(pVerifySD, &pGroup, &fDefaulted))
{
dwErr = GetLastError();
}
}
if(dwErr == ERROR_SUCCESS)
{
//
// If it's self relative, we'll have to make it absolute.
//
if(FLAG_ON(((SECURITY_DESCRIPTOR *)pSD)->Control,
SE_SELF_RELATIVE))
{
PSECURITY_DESCRIPTOR pSD2;
dwErr = MakeSDAbsolute(pSD,
SeInfo,
&pSD2,
pOwner,
pGroup);
if(dwErr == ERROR_SUCCESS)
{
AccFree(pSD);
pSD = pSD2;
}
}
else
{
if(pOwner != NULL)
{
if(SetSecurityDescriptorOwner(pOldObjSD,
pOwner,
FALSE) == FALSE)
{
dwErr = GetLastError();
}
}
if(pGroup != NULL)
{
if(SetSecurityDescriptorGroup(pOldObjSD,
pGroup,
FALSE) == FALSE)
{
dwErr = GetLastError();
}
}
}
}
}
}
if(dwErr == ERROR_SUCCESS)
{
dwErr = GetCurrentToken( &hProcessToken );
}
if(dwErr == ERROR_SUCCESS)
{
dwErr = ConvertToAutoInheritSD(pParentSD,
pOldObjSD,
TRUE,
&gRegGenMapping,
&pUpdatedSD);
if(dwErr == ERROR_SUCCESS)
{
//
// Now, if we're going to do manual propagation,
// we'll write out the old SD until we get everyone
// else updated
//
PSECURITY_DESCRIPTOR pWriteSD = pUpdatedSD;
if(fManualProp == TRUE)
{
pWriteSD = pOldObjSD;
}
else
{
if(SetPrivateObjectSecurity(SeInfo,
pParentSD,
&pUpdatedSD,
&gRegGenMapping,
hProcessToken) == FALSE)
{
dwErr = GetLastError();
}
}
//
// Reset it...
//
if(dwErr == ERROR_SUCCESS)
{
dwErr = SetRegistrySecurityInfo(hReg,
SeInfo,
NULL,
pWriteSD);
}
}
}
else
{
pVerifySD = NULL;
}
}
}
}
}
else
{
if(dwErr == ERROR_SUCCESS &&
FLAG_ON(SeInfo, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION))
{
dwErr = SetRegistrySecurityInfo(hReg,
SeInfo,
NULL,
pSD);
}
}
//
// Ok, now if we're doing propagation, we'll get busy and do that...
//
if(dwErr == ERROR_SUCCESS && fManualProp == TRUE)
{
//
// Set our protected flags. If we aren't messing with a particular acl, we'll
// pretend it's protected
//
fProtected = ((SECURITY_DESCRIPTOR *)pUpdatedSD)->Control &
~(SE_DACL_PROTECTED | SE_SACL_PROTECTED);
if(FLAG_ON(fProtected, SE_DACL_PROTECTED ) || FLAG_ON(fProtected, SE_SACL_PROTECTED ))
{
dwErr = InsertPropagationFailureEntry(FailureLogList,
0,
fProtected,
pwszPath == NULL ?
L"<Unkown Registry Root>" :
pwszPath);
}
if(!FLAG_ON(SeInfo, DACL_SECURITY_INFORMATION))
{
fProtected |= SE_DACL_PROTECTED;
}
if(!FLAG_ON(SeInfo, SACL_SECURITY_INFORMATION))
{
fProtected |= SE_SACL_PROTECTED;
}
//
// Ok, go ahead and do deep. This will possibly save us
// some storage space in the long run...
//
dwErr = PropagateRegRightsDeep(pOldObjSD,
pUpdatedSD,
SeInfo,
hReg,
pcProcessed,
pfStopFlag,
fProtected,
hProcessToken,
FailureLogList);
if(dwErr == ERROR_ACCESS_DENIED)
{
dwErr = InsertPropagationFailureEntry(FailureLogList,
dwErr,
0,
pwszPath == NULL ?
L"<Unkown Registry Root>" :
pwszPath);
}
//
// If that worked, write out our updated root security descriptor
//
if(dwErr == ERROR_SUCCESS)
{
PSECURITY_DESCRIPTOR pSet;
if(!FLAG_ON(SeInfo, OWNER_SECURITY_INFORMATION))
{
if(!SetSecurityDescriptorOwner(pSD, pOwner, FALSE))
{
dwErr = GetLastError();
}
}
if(!FLAG_ON(SeInfo, GROUP_SECURITY_INFORMATION))
{
if(!SetSecurityDescriptorGroup(pSD, pGroup, FALSE))
{
dwErr = GetLastError();
}
}
if(dwErr == ERROR_SUCCESS)
{
if(CreatePrivateObjectSecurityEx(pParentSD,
pSD,
&pSet,
NULL,
TRUE,
SEF_DACL_AUTO_INHERIT |
SEF_SACL_AUTO_INHERIT |
SEF_AVOID_OWNER_CHECK |
SEF_AVOID_PRIVILEGE_CHECK,
hProcessToken,
&gRegGenMapping) == FALSE)
{
dwErr = GetLastError();
}
else
{
dwErr = SetRegistrySecurityInfo(hReg,
SeInfo,
NULL,
pSet);
DestroyPrivateObjectSecurity(&pSet);
}
}
}
}
if(dwErr == ERROR_SUCCESS)
{
dwErr = WritePropagationFailureList(MARTAEVT_REGISTRY_PROPAGATION_FAILED,
FailureLogList,
hProcessToken);
}
RegCleanup:
AccFree(pOldObjSD);
AccFree(pVerifySD);
if(pUpdatedSD != NULL)
{
DestroyPrivateObjectSecurity(&pUpdatedSD);
}
acDebugOut((DEB_TRACE, "Out SetAndPropRegRights: %ld\n", dwErr));
return(dwErr);
}
//+---------------------------------------------------------------------------
//
// Function: ConvertRegHandleToName
//
// Synopsis: Determines the registry path for a handle. Issues an
// NtQueryInformationFile to determine the path name
//
// Arguments: [IN hKey] -- The (open) handle of the file
// object
// [OUT ppwszName] -- Where the name is returned
//
// Returns: ERROR_SUCCESS -- Succcess
// ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed
//
// Notes: The returned memory must be freed with AccFree
//
//----------------------------------------------------------------------------
DWORD
ConvertRegHandleToName(IN HKEY hKey,
OUT PWSTR *ppwszName)
{
DWORD dwErr = ERROR_SUCCESS;
//
// First, determine the size of the buffer we need...
//
BYTE pBuff[512];
ULONG cLen = 0;
POBJECT_NAME_INFORMATION pNI = NULL;
PWSTR pwszPath = NULL;
NTSTATUS Status = NtQueryObject(hKey,
ObjectNameInformation,
(POBJECT_NAME_INFORMATION)pBuff,
512,
&cLen);
if(!NT_SUCCESS(Status))
{
if(Status == STATUS_BUFFER_TOO_SMALL ||
Status == STATUS_INFO_LENGTH_MISMATCH)
{
//
// Fine.. Allocate a big enough buffer
//
pNI = (POBJECT_NAME_INFORMATION)AccAlloc(cLen);
if(pNI != NULL)
{
Status = NtQueryObject(hKey,
ObjectNameInformation,
pNI,
cLen,
NULL);
if(NT_SUCCESS(Status))
{
pwszPath = pNI->Name.Buffer;
}
AccFree(pNI);
}
else
{
Status = STATUS_NO_MEMORY;
}
}
if(dwErr == ERROR_SUCCESS)
{
dwErr = RtlNtStatusToDosError(Status);
}
}
else
{
pwszPath = ((POBJECT_NAME_INFORMATION)pBuff)->Name.Buffer;
}
//
// If we have a path, then it's a simple matter to pull out the appropriate string,
// since what gets returned is something in the form of \\REGISTRY\\MACHINE\\somepath
// which is pretty close to what we want
//
#define REG_OBJ_TAG L"\\REGISTRY\\"
if(pwszPath != NULL)
{
pwszPath += (sizeof(REG_OBJ_TAG) / sizeof(WCHAR) - 1);
ACC_ALLOC_AND_COPY_STRINGW(pwszPath,
*ppwszName,
dwErr);
}
return(dwErr);
}