Loading learning content...
In a multi-user operating system, controlling who can access what data is fundamental to security. While FAT file systems had no built-in security—anyone could read any file—NTFS was designed from the ground up with a comprehensive, Windows-integrated permission system.
NTFS permissions go far beyond simple read/write/execute flags. They support:
Understanding NTFS permissions is essential for system administrators, security professionals, and anyone building applications that interact with secured file systems.
By the end of this page, you will understand security descriptors and their components, how Discretionary and System ACLs work, the permission inheritance model, effective permissions calculation, and how Windows enforces access control at the file system level.
Every securable object in Windows—files, directories, registry keys, kernel objects—has a Security Descriptor. This data structure defines who owns the object, who can access it, and what access is logged.
Security Descriptor Components:
Owner SID:
The owner has implicit rights to change the DACL (even if the DACL denies them access). Ownership typically transfers during file creation—the creator becomes the owner. Administrators or those with SE_TAKE_OWNERSHIP_NAME privilege can take ownership.
Security Descriptors in NTFS:
NTFS stores security descriptors in the $Secure system file (MFT record 9). Rather than storing the full descriptor in each file's MFT record, NTFS uses a Security ID—a 32-bit index into $Secure. This deduplication is crucial:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
// Security Descriptor structuretypedef struct _SECURITY_DESCRIPTOR { BYTE Revision; // Must be 1 BYTE Sbz1; // Reserved WORD Control; // Control flags (see below) PSID Owner; // Owner SID (or offset if SE_SELF_RELATIVE) PSID Group; // Group SID PACL Sacl; // System ACL (auditing) PACL Dacl; // Discretionary ACL (permissions)} SECURITY_DESCRIPTOR; // Control flags#define SE_OWNER_DEFAULTED 0x0001#define SE_GROUP_DEFAULTED 0x0002#define SE_DACL_PRESENT 0x0004#define SE_DACL_DEFAULTED 0x0008#define SE_SACL_PRESENT 0x0010#define SE_SACL_DEFAULTED 0x0020#define SE_DACL_AUTO_INHERIT_REQ 0x0100#define SE_SACL_AUTO_INHERIT_REQ 0x0200#define SE_DACL_AUTO_INHERITED 0x0400#define SE_SACL_AUTO_INHERITED 0x0800#define SE_DACL_PROTECTED 0x1000 // Block inheritance#define SE_SACL_PROTECTED 0x2000#define SE_SELF_RELATIVE 0x8000 // Pointers are offsets // Security ID (stored in $STANDARD_INFORMATION)// This is an index into the $Secure file, NOT a SID!typedef DWORD SECURITY_ID; // $Secure file structure// Contains two indices:// $SDH - Security Descriptor Hash index (B+ tree)// $SII - Security ID Index (B+ tree)// $SDS - Security Descriptor Stream (actual descriptors) // Looking up security descriptor by SECURITY_IDPSECURITY_DESCRIPTOR GetSecurityDescriptor( HANDLE hVolume, SECURITY_ID securityId) { // 1. Read $Secure file's $SII index // 2. Look up entry for securityId // 3. Get offset and length from entry // 4. Read descriptor from $SDS stream at that offset // 5. Return copy of descriptor // (Simplified - actual implementation is complex) return NULL;} // Deduplication in $Secure:// When setting security on a file:// 1. Compute hash of new security descriptor// 2. Look up hash in $SDH index// 3. If found with matching descriptor: reuse that Security ID// 4. If not found: add new entry to $SDS, $SDH, $SII// 5. Store Security ID in file's $STANDARD_INFORMATIONSecurity descriptors come in two formats:
• Absolute: Contains pointers to separate SID and ACL structures in memory • Self-Relative: All data is contiguous; 'pointers' are offsets from the descriptor start
NTFS stores self-relative descriptors (they can be written as a single block). Windows APIs typically work with absolute format internally.
Security Identifiers (SIDs) are variable-length binary values that uniquely identify security principals—users, groups, and computers. Understanding SIDs is essential for working with NTFS permissions.
SID Structure:
+----------------+
| Revision (1) | Always 1
+----------------+
| SubAuthCount | Number of sub-authorities (1-15)
+----------------+
| Authority (6) | Identifier authority (e.g., NT_AUTHORITY)
+----------------+
| SubAuth[0] (4) | First sub-authority
+----------------+
| SubAuth[1] (4) | Second sub-authority
+----------------+
| ... |
+----------------+
| SubAuth[n-1] | Last sub-authority (RID)
+----------------+
SID String Format:
SIDs are commonly displayed as strings: S-R-X-Y1-Y2-...-Yn
S: Literal 'S'R: Revision (always 1)X: Identifier authorityY1..Yn: Sub-authorities (decimal)Example: S-1-5-21-3623811015-3361044348-30300820-1013
Well-Known SIDs:
| SID | Name | Description |
|---|---|---|
| S-1-0-0 | Nobody | No security principal |
| S-1-1-0 | Everyone | All users |
| S-1-2-0 | Local | Users logged on locally |
| S-1-3-0 | Creator Owner | Placeholder replaced by object creator |
| S-1-3-1 | Creator Group | Placeholder replaced by creator's group |
| S-1-5-18 | SYSTEM | LocalSystem account |
| S-1-5-19 | LocalService | Local Service account |
| S-1-5-20 | NetworkService | Network Service account |
| S-1-5-32-544 | Administrators | Built-in Administrators group |
| S-1-5-32-545 | Users | Built-in Users group |
| S-1-5-32-546 | Guests | Built-in Guests group |
| S-1-5-domain-500 | Administrator | Built-in Administrator account |
| S-1-5-domain-501 | Guest | Built-in Guest account |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
// SID structuretypedef struct _SID { BYTE Revision; BYTE SubAuthorityCount; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; DWORD SubAuthority[ANYSIZE_ARRAY]; // Variable length} SID, *PSID; // SID Identifier Authorities#define SECURITY_NULL_SID_AUTHORITY {0,0,0,0,0,0} // S-1-0#define SECURITY_WORLD_SID_AUTHORITY {0,0,0,0,0,1} // S-1-1#define SECURITY_LOCAL_SID_AUTHORITY {0,0,0,0,0,2} // S-1-2#define SECURITY_CREATOR_SID_AUTHORITY {0,0,0,0,0,3} // S-1-3#define SECURITY_NT_AUTHORITY {0,0,0,0,0,5} // S-1-5 // Convert SID to stringBOOL SidToString(PSID pSid, LPWSTR buffer, DWORD bufLen) { return ConvertSidToStringSidW(pSid, &buffer);} // Get SID from tokenBOOL GetCurrentUserSid(PSID *ppSid) { HANDLE hToken; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE; DWORD dwSize = 0; GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize); PTOKEN_USER pUser = (PTOKEN_USER)malloc(dwSize); if (!GetTokenInformation(hToken, TokenUser, pUser, dwSize, &dwSize)) { free(pUser); CloseHandle(hToken); return FALSE; } DWORD sidLen = GetLengthSid(pUser->User.Sid); *ppSid = (PSID)malloc(sidLen); CopySid(sidLen, *ppSid, pUser->User.Sid); free(pUser); CloseHandle(hToken); return TRUE;} // Check if SID is BUILTIN\AdministratorsBOOL IsAdministratorsSid(PSID pSid) { SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID pAdminSid; if (!AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, // 32 DOMAIN_ALIAS_RID_ADMINS, // 544 0, 0, 0, 0, 0, 0, &pAdminSid)) { return FALSE; } BOOL isAdmin = EqualSid(pSid, pAdminSid); FreeSid(pAdminSid); return isAdmin;}Access Control Lists (ACLs) are ordered lists of Access Control Entries (ACEs). There are two types of ACLs:
ACL Structure:
+------------------+
| ACL Revision (1) | Typically 2 or 4
+------------------+
| Sbz1 (1) | Padding
+------------------+
| ACL Size (2) | Total bytes
+------------------+
| ACE Count (2) | Number of ACEs
+------------------+
| Sbz2 (2) | Padding
+------------------+
| ACE[0] | First ACE
+------------------+
| ACE[1] | Second ACE
+------------------+
| ... |
+------------------+
Access Control Entry (ACE) Structure:
+------------------+
| ACE Type (1) | Allow, Deny, Audit, etc.
+------------------+
| ACE Flags (1) | Inheritance flags
+------------------+
| ACE Size (2) | Total bytes for this ACE
+------------------+
| Access Mask (4) | Specific rights granted/denied
+------------------+
| SID (variable) | Principal this ACE applies to
+------------------+
ACE Types:
| Type Code | Name | Description |
|---|---|---|
| 0x00 | ACCESS_ALLOWED_ACE | Grants access rights to a SID |
| 0x01 | ACCESS_DENIED_ACE | Denies access rights to a SID |
| 0x02 | SYSTEM_AUDIT_ACE | Generates audit on access attempt |
| 0x03 | SYSTEM_ALARM_ACE | Generates alarm (rarely used) |
| 0x04 | ACCESS_ALLOWED_COMPOUND_ACE | For impersonation (rarely used) |
| 0x05 | ACCESS_ALLOWED_OBJECT_ACE | Allow with object type (AD DS) |
| 0x06 | ACCESS_DENIED_OBJECT_ACE | Deny with object type (AD DS) |
| 0x11 | ACCESS_ALLOWED_CALLBACK_ACE | Allow with callback data |
ACE Ordering in DACL:
The order of ACEs matters critically. Windows evaluates ACEs in order, stopping at the first match that determines access. The recommended order is:
This ordering ensures that explicit denies take precedence over inherited allows, and explicit grants override inherited denies.
Null DACL vs Empty DACL:
This distinction is critical for security. A null DACL is extremely dangerous and should never be used in production.
A null DACL (SE_DACL_PRESENT not set, or DACL is NULL) grants full access to everyone—including anonymous users and the Everyone group. This is a critical security vulnerability. Always use an explicit DACL, even if it grants broad permissions explicitly.
The Access Mask in an ACE is a 32-bit value specifying the rights granted or denied. File-specific permissions occupy the lower 16 bits; generic and standard rights occupy the upper 16 bits.
Access Mask Layout:
31 16 15 0
+--------------------+----------------------+
| Generic/Standard | Object-Specific |
+--------------------+----------------------+
GRWX + Standard File-specific bits
| Right | Value | Description |
|---|---|---|
| FILE_READ_DATA | 0x0001 | Read file data (List folder for directories) |
| FILE_WRITE_DATA | 0x0002 | Write file data (Create files for directories) |
| FILE_APPEND_DATA | 0x0004 | Append data (Create subdirectories) |
| FILE_READ_EA | 0x0008 | Read extended attributes |
| FILE_WRITE_EA | 0x0010 | Write extended attributes |
| FILE_EXECUTE | 0x0020 | Execute file (Traverse directory) |
| FILE_DELETE_CHILD | 0x0040 | Delete child objects (directories only) |
| FILE_READ_ATTRIBUTES | 0x0080 | Read file attributes |
| FILE_WRITE_ATTRIBUTES | 0x0100 | Write file attributes |
| Right | Value | Description |
|---|---|---|
| DELETE | 0x00010000 | Delete the object |
| READ_CONTROL | 0x00020000 | Read security descriptor (not SACL) |
| WRITE_DAC | 0x00040000 | Modify the DACL |
| WRITE_OWNER | 0x00080000 | Change owner |
| SYNCHRONIZE | 0x00100000 | Use for synchronization |
| ACCESS_SYSTEM_SECURITY | 0x01000000 | Access SACL (requires privilege) |
| GENERIC_ALL | 0x10000000 | All possible access |
| GENERIC_EXECUTE | 0x20000000 | Execute access |
| GENERIC_WRITE | 0x40000000 | Write access |
| GENERIC_READ | 0x80000000 | Read access |
Generic Rights Mapping:
Generic rights are placeholders that are mapped to object-specific rights when an access check is performed:
File Generic Rights Mapping:
GENERIC_READ -> FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE
GENERIC_WRITE -> FILE_WRITE_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_APPEND_DATA | SYNCHRONIZE
GENERIC_EXECUTE -> FILE_EXECUTE | FILE_READ_ATTRIBUTES | SYNCHRONIZE
GENERIC_ALL -> All file rights
Common Permission Combinations (Windows UI):
| UI Permission | Access Mask | Included Rights |
|---|---|---|
| Read | 0x00120089 | READ_DATA, READ_EA, READ_ATTRS, READ_CONTROL, SYNC |
| Read & Execute | 0x001200A9 | Read + EXECUTE |
| Write | 0x00120116 | WRITE_DATA, APPEND_DATA, WRITE_EA, WRITE_ATTRS, READ_CONTROL, SYNC |
| Modify | 0x001301BF | Read & Execute + Write + DELETE |
| Full Control | 0x001F01FF | All rights |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
// File-specific access rights#define FILE_READ_DATA 0x0001#define FILE_WRITE_DATA 0x0002#define FILE_APPEND_DATA 0x0004#define FILE_READ_EA 0x0008#define FILE_WRITE_EA 0x0010#define FILE_EXECUTE 0x0020#define FILE_DELETE_CHILD 0x0040#define FILE_READ_ATTRIBUTES 0x0080#define FILE_WRITE_ATTRIBUTES 0x0100 // Standard access rights#define DELETE 0x00010000#define READ_CONTROL 0x00020000#define WRITE_DAC 0x00040000#define WRITE_OWNER 0x00080000#define SYNCHRONIZE 0x00100000 // Generic rights#define GENERIC_ALL 0x10000000#define GENERIC_EXECUTE 0x20000000#define GENERIC_WRITE 0x40000000#define GENERIC_READ 0x80000000 // Common combinations#define FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) #define FILE_GENERIC_READ (STANDARD_RIGHTS_READ | FILE_READ_DATA | \ FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE) #define FILE_GENERIC_WRITE (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | \ FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE) #define FILE_GENERIC_EXECUTE (STANDARD_RIGHTS_EXECUTE | \ FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE) // Decode access mask for displayvoid DisplayAccessMask(ACCESS_MASK mask) { wprintf(L"Access Mask: 0x%08X\n", mask); if (mask & FILE_READ_DATA) wprintf(L" FILE_READ_DATA\n"); if (mask & FILE_WRITE_DATA) wprintf(L" FILE_WRITE_DATA\n"); if (mask & FILE_APPEND_DATA) wprintf(L" FILE_APPEND_DATA\n"); if (mask & FILE_READ_EA) wprintf(L" FILE_READ_EA\n"); if (mask & FILE_WRITE_EA) wprintf(L" FILE_WRITE_EA\n"); if (mask & FILE_EXECUTE) wprintf(L" FILE_EXECUTE\n"); if (mask & FILE_DELETE_CHILD) wprintf(L" FILE_DELETE_CHILD\n"); if (mask & FILE_READ_ATTRIBUTES) wprintf(L" FILE_READ_ATTRIBUTES\n"); if (mask & FILE_WRITE_ATTRIBUTES) wprintf(L" FILE_WRITE_ATTRIBUTES\n"); if (mask & DELETE) wprintf(L" DELETE\n"); if (mask & READ_CONTROL) wprintf(L" READ_CONTROL\n"); if (mask & WRITE_DAC) wprintf(L" WRITE_DAC\n"); if (mask & WRITE_OWNER) wprintf(L" WRITE_OWNER\n"); if (mask & SYNCHRONIZE) wprintf(L" SYNCHRONIZE\n");}NTFS supports permission inheritance—child objects can inherit permissions from their parent containers. This dramatically simplifies permission management in directory hierarchies.
Inheritance Flags in ACEs:
Each ACE has inheritance flags that control how it propagates:
| Flag | Value | Meaning |
|---|---|---|
| OBJECT_INHERIT_ACE | 0x01 | Files in the container inherit this ACE |
| CONTAINER_INHERIT_ACE | 0x02 | Subdirectories inherit this ACE |
| NO_PROPAGATE_INHERIT_ACE | 0x04 | Don't propagate to grandchildren |
| INHERIT_ONLY_ACE | 0x08 | ACE applies only to children, not the container itself |
| INHERITED_ACE | 0x10 | This ACE was inherited (set by system) |
Inheritance Scenarios:
Scenario 1: ACE on folder with CONTAINER_INHERIT | OBJECT_INHERIT
- Applies to the folder
- New files inherit it
- New subfolders inherit it
- Grandchildren inherit it
Scenario 2: ACE with INHERIT_ONLY | OBJECT_INHERIT
- Does NOT apply to the folder itself
- New files inherit it
- Subfolders don't inherit (no CONTAINER_INHERIT)
Scenario 3: ACE with CONTAINER_INHERIT | NO_PROPAGATE_INHERIT
- Applies to the folder
- Direct child folders inherit it
- Grandchildren do NOT inherit it
Blocking Inheritance:
The SE_DACL_PROTECTED flag in the security descriptor's control bits prevents inheritance from parent containers. When set:
Inheritance Propagation:
When you change permissions on a parent folder:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
// ACE Inheritance flags#define OBJECT_INHERIT_ACE 0x01#define CONTAINER_INHERIT_ACE 0x02#define NO_PROPAGATE_INHERIT_ACE 0x04#define INHERIT_ONLY_ACE 0x08#define INHERITED_ACE 0x10 // Security descriptor control flags for inheritance#define SE_DACL_PROTECTED 0x1000#define SE_DACL_AUTO_INHERITED 0x0400 // Check if an ACE was inheritedBOOL IsInheritedAce(PACE_HEADER ace) { return (ace->AceFlags & INHERITED_ACE) != 0;} // Create an ACE that will be inherited by files onlyBOOL AddFileOnlyInheritAce(PACL pAcl, PSID pSid, ACCESS_MASK access) { // OBJECT_INHERIT: Files inherit // INHERIT_ONLY: Don't apply to this container return AddAccessAllowedAceEx( pAcl, ACL_REVISION, OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE, access, pSid );} // Disable inheritance on a file/folderBOOL DisableInheritance(LPCWSTR path, BOOL convertInheritedToExplicit) { PSECURITY_DESCRIPTOR pSD = NULL; PACL pDacl = NULL; BOOL daclPresent, daclDefaulted; // Get current security if (GetNamedSecurityInfoW( path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &pSD) != ERROR_SUCCESS) { return FALSE; } if (convertInheritedToExplicit) { // Copy inherited ACEs as explicit ACEs // Then set protected DACL PACL pNewDacl = CreateExplicitDaclFromInherited(pDacl); SetNamedSecurityInfoW( (LPWSTR)path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pNewDacl, NULL ); LocalFree(pNewDacl); } else { // Just protect DACL, removing inherited ACEs SetNamedSecurityInfoW( (LPWSTR)path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pDacl, NULL ); } LocalFree(pSD); return TRUE;} // Propagate permissions to childrenBOOL PropagatePermissions(LPCWSTR folderPath) { // This is essentially what happens when you click // "Replace all child permissions" in Windows // Use SetSecurityInfo with UNPROTECTED_DACL_SECURITY_INFORMATION // to re-enable inheritance propagation return TreeResetNamedSecurityInfoW( (LPWSTR)folderPath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, // Owner, Group NULL, // New DACL (NULL = recalculate from inheritance) NULL, // SACL FALSE, // KeepExplicit NULL, NULL, // Progress callback PROG_INVOKE_NEVER ) == ERROR_SUCCESS;}When a process attempts to access a file, Windows performs an access check to determine if the requested access should be granted. Understanding this algorithm is crucial for security analysis and troubleshooting.
Access Check Inputs:
The Algorithm:
1. If caller has SE_BACKUP_NAME or SE_RESTORE_NAME privilege and
is requesting backup/restore access, grant appropriate rights.
2. If the DACL is NULL (not present), grant all requested access.
3. If the caller's token contains the Owner SID and requested
access includes only READ_CONTROL or WRITE_DAC, grant it.
4. Initialize GrantedAccess = 0, RemainingAccess = RequestedAccess.
5. For each ACE in the DACL (in order):
a. Skip if ACE SID is not in caller's token groups
b. If ACE is ACCESS_DENIED:
- If (ACE.Mask & RemainingAccess) != 0: ACCESS DENIED
c. If ACE is ACCESS_ALLOWED:
- GrantedAccess |= (ACE.Mask & RemainingAccess)
- RemainingAccess &= ~ACE.Mask
d. If RemainingAccess == 0: break (all rights granted)
6. If RemainingAccess != 0: ACCESS DENIED
Otherwise: ACCESS GRANTED with GrantedAccess
Key Implications:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
// Perform an access checkBOOL CheckAccessToFile(LPCWSTR filePath, ACCESS_MASK desiredAccess) { PSECURITY_DESCRIPTOR pSD = NULL; HANDLE hToken = NULL; BOOL accessGranted = FALSE; ACCESS_MASK grantedAccess = 0; DWORD accessCheckResult = 0; // Get file's security descriptor DWORD sdLen = 0; GetFileSecurityW(filePath, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, NULL, 0, &sdLen); pSD = (PSECURITY_DESCRIPTOR)malloc(sdLen); if (!GetFileSecurityW(filePath, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, pSD, sdLen, &sdLen)) { free(pSD); return FALSE; } // Get an impersonation token if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) { free(pSD); return FALSE; } HANDLE hImpToken; if (!DuplicateToken(hToken, SecurityImpersonation, &hImpToken)) { CloseHandle(hToken); free(pSD); return FALSE; } // Set up generic mapping GENERIC_MAPPING fileGenericMapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; // Map generic rights to specific rights MapGenericMask(&desiredAccess, &fileGenericMapping); // Prepare privilege set PRIVILEGE_SET privSet; DWORD privSetLen = sizeof(privSet); // Perform the access check if (!AccessCheck( pSD, // Security descriptor hImpToken, // Client token desiredAccess, // Desired access &fileGenericMapping, // Generic mapping &privSet, // Privilege set &privSetLen, // Privilege set length &grantedAccess, // Granted access (output) &accessGranted // Access status (output) )) { wprintf(L"AccessCheck failed: %d\n", GetLastError()); } if (accessGranted) { wprintf(L"Access GRANTED: 0x%08X\n", grantedAccess); } else { wprintf(L"Access DENIED (wanted 0x%08X)\n", desiredAccess); } CloseHandle(hImpToken); CloseHandle(hToken); free(pSD); return accessGranted;} // Calculate effective permissions (considering all token groups)void GetEffectivePermissions(LPCWSTR path, PSID pSid) { TRUSTEE_W trustee; BuildTrusteeWithSidW(&trustee, pSid); PSECURITY_DESCRIPTOR pSD = NULL; GetNamedSecurityInfoW( path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, NULL, NULL, &pSD ); ACCESS_MASK accessMask; if (GetEffectiveRightsFromAclW( NULL, // Uses DACL from pSD &trustee, &accessMask ) == ERROR_SUCCESS) { wprintf(L"Effective access: 0x%08X\n", accessMask); DisplayAccessMask(accessMask); } LocalFree(pSD);}In Windows Explorer, right-click a file → Properties → Security → Advanced → Effective Access. This shows what permissions a specific user would have after evaluating all ACEs, group memberships, and inheritance. This is invaluable for troubleshooting 'Access Denied' issues.
The System Access Control List (SACL) controls auditing rather than access. When configured, it causes Windows to log security events when specified access is attempted.
SACL ACE Types:
| ACE Type | Purpose |
|---|---|
| SYSTEM_AUDIT_ACE | Log when specified access is attempted |
| SYSTEM_ALARM_ACE | Generate alarm (rarely used) |
| SYSTEM_MANDATORY_LABEL_ACE | Mandatory integrity label |
| SYSTEM_RESOURCE_ATTRIBUTE_ACE | Central Access Policy claims |
| SYSTEM_SCOPED_POLICY_ID_ACE | Scoped policies |
Audit ACE Flags:
Example SACL Configuration:
Goal: Audit all delete attempts on sensitive files
SACL Entry:
Type: SYSTEM_AUDIT_ACE
SID: S-1-1-0 (Everyone)
Access Mask: DELETE
Flags: SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG
Result: Event 4663 logged for every delete attempt
Mandatory Integrity Control:
Windows Vista introduced Mandatory Integrity Control (MIC)—a labeling system stored in the SACL. Every process and object has an integrity level:
| Level | Value | Typical Use |
|---|---|---|
| Untrusted | 0x0000 | Anonymous processes |
| Low | 0x1000 | Protected Mode IE, sandboxed apps |
| Medium | 0x2000 | Standard user applications |
| High | 0x3000 | Administrator applications |
| System | 0x4000 | System services |
A process can only modify objects at its own integrity level or lower, regardless of DACL permissions.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
// SACL ACE flags#define SUCCESSFUL_ACCESS_ACE_FLAG 0x40#define FAILED_ACCESS_ACE_FLAG 0x80 // Add an audit ACE to a file's SACLBOOL AddAuditAce(LPCWSTR filePath, PSID pSid, ACCESS_MASK auditMask) { PACL pOldSacl = NULL; PSECURITY_DESCRIPTOR pSD = NULL; // Get current SACL (requires SE_SECURITY_NAME privilege) if (GetNamedSecurityInfoW( filePath, SE_FILE_OBJECT, SACL_SECURITY_INFORMATION, NULL, NULL, NULL, &pOldSacl, &pSD) != ERROR_SUCCESS) { return FALSE; } // Calculate new ACL size DWORD aclSize = sizeof(ACL) + sizeof(SYSTEM_AUDIT_ACE) - sizeof(DWORD) + GetLengthSid(pSid); if (pOldSacl) { aclSize += pOldSacl->AclSize; } PACL pNewSacl = (PACL)malloc(aclSize); InitializeAcl(pNewSacl, aclSize, ACL_REVISION); // Copy existing ACEs if (pOldSacl) { for (DWORD i = 0; i < pOldSacl->AceCount; i++) { PACE_HEADER ace; GetAce(pOldSacl, i, (LPVOID*)&ace); AddAce(pNewSacl, ACL_REVISION, MAXDWORD, ace, ace->AceSize); } } // Add new audit ACE (audit both success and failure) AddAuditAccessAceEx( pNewSacl, ACL_REVISION, SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG | CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, auditMask, pSid, TRUE, // Audit success TRUE // Audit failure ); // Set the new SACL DWORD result = SetNamedSecurityInfoW( (LPWSTR)filePath, SE_FILE_OBJECT, SACL_SECURITY_INFORMATION, NULL, NULL, NULL, pNewSacl ); free(pNewSacl); LocalFree(pSD); return result == ERROR_SUCCESS;} // Get integrity level of a fileDWORD GetFileIntegrityLevel(LPCWSTR filePath) { PSECURITY_DESCRIPTOR pSD = NULL; PACL pSacl = NULL; if (GetNamedSecurityInfoW( filePath, SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, &pSacl, &pSD) != ERROR_SUCCESS) { return 0; } DWORD integrityLevel = SECURITY_MANDATORY_MEDIUM_RID; // Default if (pSacl && pSacl->AceCount > 0) { PSYSTEM_MANDATORY_LABEL_ACE pAce; GetAce(pSacl, 0, (LPVOID*)&pAce); if (pAce->Header.AceType == SYSTEM_MANDATORY_LABEL_ACE_TYPE) { PSID pSid = &pAce->SidStart; integrityLevel = *GetSidSubAuthority(pSid, 0); } } LocalFree(pSD); return integrityLevel;} // Set integrity level on a fileBOOL SetFileIntegrityLevel(LPCWSTR filePath, DWORD level) { PSID pIntegritySid = NULL; WCHAR sidString[64]; // Build integrity SID string swprintf_s(sidString, 64, L"S-1-16-%d", level); if (!ConvertStringSidToSidW(sidString, &pIntegritySid)) { return FALSE; } // Create SACL with mandatory label PACL pSacl = (PACL)malloc(256); InitializeAcl(pSacl, 256, ACL_REVISION); AddMandatoryAce( pSacl, ACL_REVISION, NO_PROPAGATE_INHERIT_ACE, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, // Policy pIntegritySid ); DWORD result = SetNamedSecurityInfoW( (LPWSTR)filePath, SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pSacl ); free(pSacl); LocalFree(pIntegritySid); return result == ERROR_SUCCESS;}Understanding how to work with NTFS permissions in real-world scenarios is essential for system administration and security.
Common Permission Issues:
"Access Denied" despite apparent permissions: Usually caused by:
Permissions not propagating to children: Check for:
Admin still denied access: Remember:
Command-Line Tools:
# View permissions
icacls C:\folder
# Grant user Full Control
icacls C:\folder /grant UserName:F
# Deny user Write
icacls C:\folder /deny UserName:W
# Remove all permissions for user
icacls C:\folder /remove UserName
# Reset to inherited permissions
icacls C:\folder /reset
# Take ownership
takeown /f C:\folder /r /d y
# Grant Administrators full control recursively
icacls C:\folder /grant Administrators:F /t
# View SACL (requires elevation)
icacls C:\folder /findsid Everyone /c /q
| Character | Permission | Access Mask |
|---|---|---|
| N | No access | 0 |
| F | Full control | 0x1F01FF |
| M | Modify | 0x1301BF |
| RX | Read and Execute | 0x1200A9 |
| R | Read | 0x120089 |
| W | Write | 0x120116 |
| D | Delete | 0x010000 |
| DE | Delete child | 0x000040 |
| RC | Read Control | 0x020000 |
| WDAC | Write DAC | 0x040000 |
| WO | Write Owner | 0x080000 |
| S | Synchronize | 0x100000 |
PowerShell offers richer ACL manipulation:
# Get ACL
Get-Acl C:\folder | Format-List
# Set ACL from another file
$acl = Get-Acl C:\source
Set-Acl C:\target -AclObject $acl
# Add an access rule
$acl = Get-Acl C:\folder
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
'DOMAIN\User', 'FullControl', 'Allow')
$acl.AddAccessRule($rule)
Set-Acl C:\folder -AclObject $acl
We've explored NTFS permissions in depth—the comprehensive access control system that makes NTFS suitable for enterprise environments. Let's consolidate our understanding:
Module Complete:
You have now completed the NTFS module! You understand:
These concepts form the foundation for working with Windows storage systems, whether you're developing applications, administering systems, conducting forensic investigations, or securing enterprise environments.
Congratulations! You've mastered NTFS—Windows' enterprise-grade file system. From the MFT's elegant attribute-based design to the comprehensive permission system, you now have the knowledge to work effectively with Windows storage at any level. The next module will explore file system comparisons and help you choose the right file system for different scenarios.