Fix unixmode to handle setuid and sticky when file is not an executable (#20366)

This commit is contained in:
James Truher [MSFT]
2023-10-11 23:54:54 +00:00
committed by GitHub
parent ea01a28490
commit 81a7f6f621
2 changed files with 63 additions and 65 deletions
@@ -594,82 +594,69 @@ namespace System.Management.Automation
private const char CanRead = 'r';
private const char CanWrite = 'w';
private const char CanExecute = 'x';
// helper for getting unix mode
private readonly Dictionary<StatMask, char> 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<ItemType, char> itemTypeTable = new()
private static readonly Dictionary<ItemType, char> 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";
/// <summary>Convert the mode to a string which is usable in our formatting.</summary>
/// <returns>The mode converted into a Unix style string similar to the output of ls.</returns>
public string GetModeString()
{
int offset = 0;
char[] modeCharacters = new char[10];
modeCharacters[offset++] = itemTypeTable[ItemType];
foreach (StatMask permission in permissions)
// On an Ubuntu system (docker), these 3 are roughly 70% of all the permissions
if ((Mode & 0xFFF) == 292)
{
// 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] = '-';
}
offset++;
return OwnerReadGroupReadOtherRead;
}
if ((Mode & 0xFFF) == 420)
{
return OwnerReadWriteGroupReadOtherRead;
}
if (ItemType == ItemType.Directory & (Mode & 0xFFF) == 493)
{
return DirectoryOwnerFullGroupReadExecOtherReadExec;
}
Span<char> 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);
}
@@ -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 '<Mode>' string correctly as '<Perm>'" -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" {