Skip to content

Commit c368383

Browse files
authored
Merge commit from fork
* Detect icacls along with cmd.exe * Set permissions at the beginning of the installation process * Add comments explaining permission restrictions * Add news file * Add explanation for why users with deny directives are skipped * Abort if icacls command fails * Add user domain to grant full access rights to installing user
1 parent 0cb0f5f commit c368383

2 files changed

Lines changed: 120 additions & 38 deletions

File tree

constructor/nsis/main.nsi.tmpl

Lines changed: 100 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ var /global StdOutHandleSet
5959
!include "x64.nsh"
6060

6161
!include "FileFunc.nsh"
62+
!include "StrFunc.nsh"
63+
${Using:StrFunc} StrStr
6264
!insertmacro GetParameters
6365
!insertmacro GetOptions
6466

@@ -133,18 +135,22 @@ var /global InstMode # 0 = Just Me, 1 = All Users.
133135
!define ALL_USERS 1
134136

135137
var /global CMD_EXE
138+
var /global ICACLS_EXE
136139

137-
!macro FindCmdExe
140+
!macro FindWindowsBinaries
138141
# Find cmd.exe
139142
ReadEnvStr $R0 SystemRoot
140143
ReadEnvStr $R1 windir
141144
${If} ${FileExists} "$R0"
142145
StrCpy $CMD_EXE "$R0\System32\cmd.exe"
146+
StrCpy $ICACLS_EXE "$R0\System32\icacls.exe"
143147
${ElseIf} ${FileExists} "$R1"
144148
StrCpy $CMD_EXE "$R1\System32\cmd.exe"
149+
StrCpy $ICACLS_EXE "$R1\System32\icacls.exe"
145150
${Else}
146-
# Cross our fingers CMD is in PATH
151+
# Cross our fingers binaries are in PATH
147152
StrCpy $CMD_EXE "cmd.exe"
153+
StrCpy $ICACLS_EXE "icacls.exe"
148154
${EndIf}
149155
!macroend
150156

@@ -1235,18 +1241,88 @@ FunctionEnd
12351241
!insertmacro AbortRetryNSExecWaitMacro ""
12361242
!insertmacro AbortRetryNSExecWaitMacro "un."
12371243

1244+
!macro setInstdirPermissions
1245+
# To address CVE-2022-26526.
1246+
# Revoke the write permission on directory "$INSTDIR" for Users. Users are:
1247+
# AU - authenticated users (NT AUTHORITY\Authenticated Users)
1248+
# BU - built-in (local) users (BUILTIN\Users)
1249+
# DU - domain users (<domain name>\DOMAIN USERS)
1250+
# This also applies for single-user installations to avoid giving other users
1251+
# full access on shared drives.
1252+
${If} ${UAC_IsAdmin}
1253+
StrCpy $0 "(AU) (BU) (DU)"
1254+
${Else}
1255+
# Not every directory grants write access to Users (e.g., %USERPROFILE%),
1256+
# so test whether user groups have the necessary rights.
1257+
nsExec::ExecToStack '"$ICACLS_EXE" "$INSTDIR"'
1258+
Pop $R0
1259+
Pop $R1
1260+
${If} $R0 != "0"
1261+
StrCpy $R1 \
1262+
"Unable to determine the defaults permissions of the installation directory. "\
1263+
"Ensure that you have read access to $INSTDIR and icacls.exe is in your PATH."
1264+
${Print} $R1
1265+
Abort
1266+
${EndIf}
1267+
StrCpy $0 ""
1268+
StrCpy $R2 "NT AUTHORITY\Authenticated Users|BUILTIN\Users|\Domain Users"
1269+
StrCpy $R3 "(AU)|(BU)|(DU)"
1270+
StrCpy $R4 1
1271+
loop_single_user_default_access:
1272+
${WordFind} $R2 "|" "E+$R4" $R5
1273+
${WordFind} $R3 "|" "E+$R4" $R6
1274+
IfErrors endloop_single_user_default_access
1275+
${StrStr} $R7 $R1 $R5
1276+
${If} $R7 == ""
1277+
goto increment_loop_single_user_default_access
1278+
${EndIf}
1279+
# If the user group has a deny permission directive, do not change permissions.
1280+
# Granting (RX) permissions may increase the permissions that are inherited and
1281+
# it is very unlikely that a user is granted write permissions but denied others.
1282+
${StrStr} $R7 $R1 "$R5:(D)"
1283+
${If} $R7 != ""
1284+
goto increment_loop_single_user_default_access
1285+
${EndIf}
1286+
StrCpy $0 "$0 $R6"
1287+
increment_loop_single_user_default_access:
1288+
IntOp $R4 $R4 + 1
1289+
goto loop_single_user_default_access
1290+
endloop_single_user_default_access:
1291+
${EndIf}
1292+
AccessControl::DisableFileInheritance "$INSTDIR"
1293+
${If} $0 != ""
1294+
StrCpy $1 1
1295+
loop_access:
1296+
${WordFind} $0 " " "E+$1" $2
1297+
IfErrors endloop_access
1298+
AccessControl::RevokeOnFile "$INSTDIR" "$2" "GenericWrite"
1299+
AccessControl::SetOnFile "$INSTDIR" "$2" "GenericRead + GenericExecute"
1300+
IntOp $1 $1 + 1
1301+
goto loop_access
1302+
endloop_access:
1303+
${EndIf}
1304+
${IfNot} ${UAC_IsAdmin}
1305+
# Ensure that creator has full access
1306+
ReadEnvStr $R0 USERDOMAIN
1307+
ReadEnvStr $R1 USERNAME
1308+
${If} $R0 == ""
1309+
AccessControl::SetOnFile "$INSTDIR" "$R1" "FullAccess"
1310+
${Else}
1311+
AccessControl::SetOnFile "$INSTDIR" "$R0\$R1" "FullAccess"
1312+
${EndIf}
1313+
${EndIf}
1314+
!macroend
1315+
12381316
# Installer sections
12391317
Section "Install"
12401318
${LogSet} on
12411319
${If} ${Silent}
12421320
call OnDirectoryLeave
12431321
${EndIf}
12441322

1245-
!insertmacro FindCmdExe
1323+
!insertmacro FindWindowsBinaries
12461324

1247-
SetOutPath "$INSTDIR\Lib"
1248-
File "{{ NSIS_DIR }}\_nsis.py"
1249-
File "{{ NSIS_DIR }}\_system_path.py"
1325+
SetOutPath "$INSTDIR"
12501326

12511327
# Resolve INSTDIR so that paths and registry keys do not contain '..' or similar strings.
12521328
# $0 is empty if the directory doesn't exist, but the File commands should have created it already.
@@ -1257,6 +1333,15 @@ Section "Install"
12571333
${EndIf}
12581334
StrCpy $INSTDIR $0
12591335

1336+
# Restrict permissions immediately after creating $INSTDIR
1337+
# If not, the installation directory may inherit write-permissions
1338+
# for users even during an all-users installation.
1339+
!insertmacro setInstdirPermissions
1340+
1341+
SetOutPath "$INSTDIR\Lib"
1342+
File "{{ NSIS_DIR }}\_nsis.py"
1343+
File "{{ NSIS_DIR }}\_system_path.py"
1344+
12601345
{%- if has_license %}
12611346
SetOutPath "$INSTDIR"
12621347
File {{ licensefile }}
@@ -1498,37 +1583,14 @@ Section "Install"
14981583

14991584
WriteUninstaller "$INSTDIR\Uninstall-${NAME}.exe"
15001585

1501-
# To address CVE-2022-26526.
1502-
# Revoke the write permission on directory "$INSTDIR" for Users if this is
1503-
# being run with administrative privileges. Users are:
1504-
# AU - authenticated users
1505-
# BU - built-in (local) users
1506-
# DU - domain users
1507-
${If} ${UAC_IsAdmin}
1508-
${Print} "Setting installation directory permissions..."
1509-
AccessControl::DisableFileInheritance "$INSTDIR"
1510-
# Enable inheritance on all files inside $INSTDIR.
1511-
# Use icacls because it is much faster than custom NSIS solutions.
1512-
# We continue on error because icacls fails on broken links.
1513-
ReadEnvStr $0 SystemRoot
1514-
ReadEnvStr $1 windir
1515-
${If} ${FileExists} "$0"
1516-
push '"$0\System32\icacls.exe" "$INSTDIR\*" /inheritance:e /T /C /Q'
1517-
${ElseIf} ${FileExists} "$1"
1518-
push '"$1\System32\icacls.exe" "$INSTDIR\*" /inheritance:e /T /C /Q'
1519-
${Else}
1520-
# Cross our fingers icacls is in PATH
1521-
push 'icacls.exe "$INSTDIR\*" /inheritance:e /T /C /Q'
1522-
${EndIf}
1523-
push 'Failed to enable inheritance for all files in the installation directory.'
1524-
push 'NoLog'
1525-
call AbortRetryNSExecWait
1526-
AccessControl::RevokeOnFile "$INSTDIR" "(AU)" "GenericWrite"
1527-
AccessControl::RevokeOnFile "$INSTDIR" "(DU)" "GenericWrite"
1528-
AccessControl::RevokeOnFile "$INSTDIR" "(BU)" "GenericWrite"
1529-
AccessControl::SetOnFile "$INSTDIR" "(BU)" "GenericRead + GenericExecute"
1530-
AccessControl::SetOnFile "$INSTDIR" "(DU)" "GenericRead + GenericExecute"
1531-
${EndIf}
1586+
${Print} "Setting installation directory permissions..."
1587+
# Enable inheritance on all files inside $INSTDIR.
1588+
# Use icacls because it is much faster than custom NSIS solutions.
1589+
# We continue on error because icacls fails on broken links.
1590+
push '"$ICACLS_EXE" "$INSTDIR\*" /inheritance:e /T /C /Q'
1591+
push 'Failed to enable inheritance for all files in the installation directory.'
1592+
push 'NoLog'
1593+
call AbortRetryNSExecWait
15321594
${Print} "Done!"
15331595
SectionEnd
15341596

@@ -1553,7 +1615,7 @@ Section "Uninstall"
15531615
!insertmacro un.ParseCommandLineArgs
15541616
${EndIf}
15551617

1556-
!insertmacro FindCmdExe
1618+
!insertmacro FindWindowsBinaries
15571619

15581620
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_ROOT_PREFIX", "$INSTDIR")".r0'
15591621
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
### Enhancements
2+
3+
* <news item>
4+
5+
### Bug fixes
6+
7+
* EXE: Remove write access for users during the installation process.
8+
* EXE: Remove write access for users except for the installing user from single-user installations.
9+
10+
### Deprecations
11+
12+
* <news item>
13+
14+
### Docs
15+
16+
* <news item>
17+
18+
### Other
19+
20+
* <news item>

0 commit comments

Comments
 (0)