From 5aba652e0b7d68ee29aa225bc4f6332d6a63bc73 Mon Sep 17 00:00:00 2001 From: "James Truher [MSFT]" Date: Wed, 11 Oct 2023 16:54:54 -0700 Subject: [PATCH] Fix `unixmode` to handle `setuid` and `sticky` when file is not an executable (#20366) --- .../CoreCLR/CorePsPlatform.cs | 109 ++++++++---------- .../UnixStat.Tests.ps1 | 15 ++- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index 783afe62915..dc5db5f2c48 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -594,82 +594,69 @@ public class CommonStat private const char CanRead = 'r'; private const char CanWrite = 'w'; private const char CanExecute = 'x'; - - // helper for getting unix mode - private readonly Dictionary modeMap = new() - { - { StatMask.OwnerRead, CanRead }, - { StatMask.OwnerWrite, CanWrite }, - { StatMask.OwnerExecute, CanExecute }, - { StatMask.GroupRead, CanRead }, - { StatMask.GroupWrite, CanWrite }, - { StatMask.GroupExecute, CanExecute }, - { StatMask.OtherRead, CanRead }, - { StatMask.OtherWrite, CanWrite }, - { StatMask.OtherExecute, CanExecute }, - }; - - private readonly StatMask[] permissions = new StatMask[] - { - StatMask.OwnerRead, - StatMask.OwnerWrite, - StatMask.OwnerExecute, - StatMask.GroupRead, - StatMask.GroupWrite, - StatMask.GroupExecute, - StatMask.OtherRead, - StatMask.OtherWrite, - StatMask.OtherExecute - }; + private const char NoPerm = '-'; + private const char SetAndExec = 's'; + private const char SetAndNotExec = 'S'; + private const char StickyAndExec = 't'; + private const char StickyAndNotExec = 'T'; // The item type and the character representation for the first element in the stat string - private readonly Dictionary itemTypeTable = new() + private static readonly Dictionary itemTypeTable = new() { - { ItemType.BlockDevice, 'b' }, + { ItemType.BlockDevice, 'b' }, { ItemType.CharacterDevice, 'c' }, - { ItemType.Directory, 'd' }, - { ItemType.File, '-' }, - { ItemType.NamedPipe, 'p' }, - { ItemType.Socket, 's' }, - { ItemType.SymbolicLink, 'l' }, + { ItemType.Directory, 'd' }, + { ItemType.File, '-' }, + { ItemType.NamedPipe, 'p' }, + { ItemType.Socket, 's' }, + { ItemType.SymbolicLink, 'l' }, }; + // We'll create a few common mode strings here to reduce allocations and improve performance a bit. + private const string OwnerReadGroupReadOtherRead = "-r--r--r--"; + private const string OwnerReadWriteGroupReadOtherRead = "-rw-r--r--"; + private const string DirectoryOwnerFullGroupReadExecOtherReadExec = "drwxr-xr-x"; + /// Convert the mode to a string which is usable in our formatting. /// The mode converted into a Unix style string similar to the output of ls. public string GetModeString() { - int offset = 0; - char[] modeCharacters = new char[10]; - modeCharacters[offset++] = itemTypeTable[ItemType]; + // On an Ubuntu system (docker), these 3 are roughly 70% of all the permissions + if ((Mode & 0xFFF) == 292) + { + return OwnerReadGroupReadOtherRead; + } - foreach (StatMask permission in permissions) + if ((Mode & 0xFFF) == 420) { - // determine whether we are setuid, sticky, or the usual rwx. - if ((Mode & (int)permission) == (int)permission) - { - if ((permission == StatMask.OwnerExecute && IsSetUid) || (permission == StatMask.GroupExecute && IsSetGid)) - { - // Check for setuid and add 's' - modeCharacters[offset] = 's'; - } - else if (permission == StatMask.OtherExecute && IsSticky && (ItemType == ItemType.Directory)) - { - // Directories are sticky, rather than setuid - modeCharacters[offset] = 't'; - } - else - { - modeCharacters[offset] = modeMap[permission]; - } - } - else - { - modeCharacters[offset] = '-'; - } + return OwnerReadWriteGroupReadOtherRead; + } - offset++; + if (ItemType == ItemType.Directory & (Mode & 0xFFF) == 493) + { + return DirectoryOwnerFullGroupReadExecOtherReadExec; } + Span modeCharacters = stackalloc char[10]; + modeCharacters[0] = itemTypeTable[ItemType]; + bool isExecutable; + + UnixFileMode modeInfo = (UnixFileMode)Mode; + modeCharacters[1] = modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm; + modeCharacters[2] = modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm; + isExecutable = modeInfo.HasFlag(UnixFileMode.UserExecute); + modeCharacters[3] = modeInfo.HasFlag(UnixFileMode.SetUser) ? (isExecutable ? SetAndExec : SetAndNotExec) : (isExecutable ? CanExecute : NoPerm); + + modeCharacters[4] = modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm; + modeCharacters[5] = modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm; + isExecutable = modeInfo.HasFlag(UnixFileMode.GroupExecute); + modeCharacters[6] = modeInfo.HasFlag(UnixFileMode.SetGroup) ? (isExecutable ? SetAndExec : SetAndNotExec) : (isExecutable ? CanExecute : NoPerm); + + modeCharacters[7] = modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm; + modeCharacters[8] = modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm; + isExecutable = modeInfo.HasFlag(UnixFileMode.OtherExecute); + modeCharacters[9] = modeInfo.HasFlag(UnixFileMode.StickyBit) ? (isExecutable ? StickyAndExec : StickyAndNotExec) : (isExecutable ? CanExecute : NoPerm); + return new string(modeCharacters); } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1 index b14df30e7e2..fcefb707a2e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1 @@ -37,6 +37,10 @@ Describe "UnixFileSystem additions" -Tag "CI" { @{ Mode = '555'; Perm = '-r-xr-xr-x'; Item = "${testFile}" }, @{ Mode = '666'; Perm = '-rw-rw-rw-'; Item = "${testFile}" }, @{ Mode = '777'; Perm = '-rwxrwxrwx'; Item = "${testFile}" }, + @{ Mode = '4644'; Perm = '-rwSr--r--'; Item = "${testFile}" }, + @{ Mode = '1644'; Perm = '-rw-r--r-T'; Item = "${testFile}" }, + @{ Mode = '2644'; Perm = '-rw-r-Sr--'; Item = "${testFile}" }, + @{ Mode = '7644'; Perm = '-rwSr-Sr-T'; Item = "${testFile}" }, @{ Mode = '4777'; Perm = '-rwsrwxrwx'; Item = "${testFile}" }, @{ Mode = '1777'; Perm = 'drwxrwxrwt'; Item = "${testDir}" } } @@ -57,9 +61,16 @@ Describe "UnixFileSystem additions" -Tag "CI" { It "Should present filemode '' string correctly as ''" -testCase $testCase { param ($Mode, $Perm, $Item ) + # chmod can fail for some modes so be sure to handle that here. + # specifically, when setting setgid chmod can fail if the group is privileged. chmod "$Mode" "${Item}" - $i = Get-Item $Item - $i.UnixMode | Should -Be $Perm + if ($LASTEXITCODE -ne 0) { + set-itresult -skip -because "chmod '$mode' failed" + } + else { + $i = Get-Item $Item + $i.UnixMode | Should -BeExactly $Perm + } } It "Should retrieve the user name for the file" {