Keep the home directory and the files in it from being deleted

The user could delete the whole home directory, which takes the website
down with it: sshd cannot log in without it. The home directory and the
files that have to stay in it now belong to the administrators, so the
user may change them but not remove them. Their own files stay theirs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zebreus 2026-10-25 15:04:48 +01:00
commit bc7cb26b61

View file

@ -59,15 +59,44 @@ function Restore-Keys($target, $source) {
[IO.File]::WriteAllText($target, (($keys + $required) -join "`n") + "`n")
}
# The permissions of the home directory and of the files in it that have to stay: the user may read and write them
# and delete their own files, but not the home directory itself and not the files the website needs. Only SYSTEM
# and the administrators own them, so the user cannot take the permissions away again.
function Set-ProtectedAcl($path, $forDirectory) {
if (-not $sid) { return }
try {
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
$acl = Get-Acl -LiteralPath $path
$acl.SetAccessRuleProtection($true, $false)
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
$acl.SetOwner($admins)
$none = [System.Security.AccessControl.InheritanceFlags]::None
$children = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$noPropagate = [System.Security.AccessControl.PropagationFlags]::None
$inheritOnly = [System.Security.AccessControl.PropagationFlags]::InheritOnly
$full = [System.Security.AccessControl.FileSystemRights]::FullControl
$some = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write
$inherit = if ($forDirectory) { $children } else { $none }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $some, $none, $noPropagate, 'Allow'))
if ($forDirectory) {
# the files in it are the user's own, they may delete them
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $full, $children, $inheritOnly, 'Allow'))
}
Set-Acl -LiteralPath $path -AclObject $acl
} catch {
Write-Log "the permissions of $path are not set: $_"
}
}
# The home directory is the one from /etc/passwd, which the user cannot change, but which they can delete. A new one
# gets MSYS2's default files from /etc/skel, which MSYS2 itself only copies into a home that does not exist yet.
function New-Home($path) {
New-Item -ItemType Directory -Path $path | Out-Null
# The parent directory gives the users read access only, so the owner gets write access on their own home.
if ($sid) {
icacls $path /grant "*${sid}:(OI)(CI)F" | Out-Null
icacls $path /setowner "*$sid" | Out-Null
}
Set-ProtectedAcl $path $true
Get-ChildItem -LiteralPath 'C:\msys2\etc\skel' -Force | Copy-Item -Destination $path -Recurse -Force
}
@ -78,6 +107,8 @@ if (-not (Test-Path -LiteralPath $HomeDir)) {
Remove-Anything $HomeDir
New-Home $HomeDir
Write-Log "replaced the link $HomeDir"
} else {
Set-ProtectedAcl $HomeDir $true
}
foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
@ -88,10 +119,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
Remove-Anything $dir
}
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
if ($sid) {
icacls $dir /grant "*${sid}:(OI)(CI)F" | Out-Null
icacls $dir /setowner "*$sid" | Out-Null
}
Set-ProtectedAcl $dir $true
if (Test-Path -LiteralPath $target) {
$item = Get-Item -LiteralPath $target -Force
if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
@ -100,7 +128,6 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
} else {
# the user may have locked the file; taking it back lets us write it
takeown /f $target /a | Out-Null
icacls $target /reset | Out-Null
}
}
if ($copy.Name -eq 'authorized_keys') {
@ -108,7 +135,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
} else {
Copy-Item -LiteralPath $copy.FullName -Destination $target -Force
}
if ($sid) { icacls $target /setowner "*$sid" | Out-Null }
Set-ProtectedAcl $target $false
Write-Log "restored $($copy.Name)"
} catch {
Write-Log "failed $($copy.Name) : $_"