Discussing the nuts and bolts of software development

Wednesday, May 14, 2008

 

Chmod-me Win32 - A quick look at NTFS file system permissions

Even if NTFS is the de-facto standard file system on Windows machines today, the NTFS security model uses a set of concepts that are somewhat unfamiliar to most of us or may seem familiar until we actually use them, programmatically.

A while ago, I installed a C++ application I wrote using a local administrator account on an WinXP machine. Later, I tested the application using a restricted user account and got into a situation where a "config.ini" file, copied to the Windows shared application data folder the first time the application was launched, couldn't be modified. I quickly figured that fixing the problem would just be a matter of setting the proper "config.ini" file permissions since the file, being initially copied in an Administrator security context, wouldn't have the proper permissions to be modified by a restricted user.
In the Unix world, a shell command called 'chmod' sets file access permissions for user and groups. It's simple easy, simple, effective and on a C++ program, a single system call is all you need.
Now in Windows, the NTFS security model has a much finer grain, so there's multiple things to consider:

1) In NTFS permission are chained in a list, for files permissions that list is a Discretionary Access Control List (DACLs).

2) An Access Control List contain one or more Access Control Entries (ACE) which allows to grant or deny specifics permissions. Since file permissions can inherit permissions from parent folders (provided the parent folder allows permissions to be inherited), file permission can either be granted or denied.

3) An ACE uses Security Identifiers (SIDs) to identify a user or group.

To change the permission of a single file in a Win32 C++ program, you may end-up coding something like this:

Notes:
1) For the sake of clarity, only the *Unicode* character set is used in this example.
2) The header files "AclApi.h" and "Sddl.h" are required."
3) _WIN32_WINNT 0x0500" needs to be added to your project "Preprocessor Definitions" settings.
/*******************************************************************************
*
* FUNCTION SetFilePermissions
*
* DESCRIPTION Sets file permissions for a specific file
*
* PARAMETERS string filename: full pathname of the file to change permissions
* string username: name of a user or a group
* int permissions: can be one or more of the following:
* {GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE OR GENERIC_ALL}
*
* If a permission is omitted and is currently associated
* with the specified file, it will be removed,
* unless that permission is inherited.
*
* RETURNS non-zero if succeeds, zero if it fails.
* Use GetLastError() to get extended the error information.
*
******************************************************************************/
bool SetFilePermissions(LPCWSTR filename, LPCWSTR username, int permissions)
{
SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
EXPLICIT_ACCESS eAcc;

PSID pSid = NULL;
PACL dacl = NULL;
int lRes = ERROR_SUCCESS;

eAcc.grfAccessMode = GRANT_ACCESS;
eAcc.grfAccessPermissions = permissions;
eAcc.grfInheritance = OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
eAcc.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
eAcc.Trustee.pMultipleTrustee = NULL;
eAcc.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;

// NOTE: In some cases, you will want to use a "well-known security identifiers"
// (http://support.microsoft.com/kb/243330) instead of a username or group
// since SIDs remain the same from one operating system language to another.
if( ConvertStringSidToSid(username, &pSid) )
{
eAcc.Trustee.TrusteeForm = TRUSTEE_IS_SID;
eAcc.Trustee.ptstrName = static_cast(pSid);
}
else
{
// Reset lasterror since ConvertSidToStringSid() is also used
// to determine if a username is a SID or not.
SetLastError(0);
eAcc.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
eAcc.Trustee.ptstrName = const_cast(username);
}

// Create a DACL
lRes = SetEntriesInAcl(1, &eAcc, NULL, &dacl);
if (lRes == ERROR_SUCCESS)
{
// Set DACL
lRes = SetNamedSecurityInfo( const_cast(filename), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION, NULL, NULL, dacl, NULL);
}

if (pSid != NULL)
LocalFree((HLOCAL)pSid);

if (dacl != NULL)
LocalFree((HLOCAL)dacl);

return lRes == ERROR_SUCCESS;
}

int wmain(int argc, WCHAR* argv[])
{
// As an example, let's allow "Read" and "Write" permissions to the group
// "Everyone" for the file "myconfig.ini". Since the actual name "Everyone"
// depends on the actual operating system language, we'll use its
// matching SID string representation (S-1-1-0) instead.
bool success = SetFilePermissions(
L"C:/Documents and Settings/All Users/Application Data/myapp/myconfig.ini",
L"S-1-1-0", GENERIC_READ | GENERIC_WRITE);

// For security purposes, It might make more sense to allow only
// authenticated users ( SID: S-1-5-11 ) instead of the group "Everyone".

return !success; // zero means the program ran successfully
}
The Windows security model isn't trivial, but fortunately some good articles have been published on the subject. The following gives a good overviews of permissions precedence and this one provides more details about ACL Inheritance. For a more in-dept API coverage, please refer to the MSDN documentation .

You if are in a hurry you can always get away by using the real Microsoft chmod command line equivalent - CACLS.EXE in a script.

Happy Chmoding - Thanks Mikhail for your input.

Labels: , , ,


Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?