This is an automated email from the ASF dual-hosted git repository. jimjag pushed a commit to branch AOO50X in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit f333193f20a1751d0fe2d27f0669b087fd605541 Author: Jim Jagielski <[email protected]> AuthorDate: Thu Sep 10 12:06:16 2026 -0400 macOS: harden application and disk image signing Make strict signature verification fail packaging, honor configured keychains for all artifacts, and limit process entitlements to executable code. Reject unresolved build-time Mach-O load paths before signing. Use safe argument handling for signing and disk image creation, make language-pack wrappers valid application bundles, and prevent them from invalidating signed installations. Relocate bundled Python's install names, preserve its Darwin sysconfig data, and update the Spotlight importer for the Contents/program layout. (cherry picked from commit d884924344e6b15164cf98f20d9dbdded7540356) --- main/configure.ac | 4 +- .../extensions/source/macosx/spotlight/makefile.mk | 2 +- main/python/makefile.mk | 8 ++ main/python/prj/d.lst | 7 +- main/setup_native/scripts/mac_install.script | 2 +- .../scripts/osx_install_languagepack.applescript | 11 +++ .../scripts/osx_install_patch.applescript | 10 +++ main/setup_native/source/mac/macinstall.ulf | 3 + main/solenv/bin/macosx-codesign.sh | 91 +++++++++++++++++----- main/solenv/bin/modules/installer/simplepackage.pm | 32 ++++++-- 10 files changed, 137 insertions(+), 33 deletions(-) diff --git a/main/configure.ac b/main/configure.ac index f80d9267bb..bbba0d6c63 100644 --- a/main/configure.ac +++ b/main/configure.ac @@ -987,6 +987,9 @@ AC_ARG_WITH(macosx-codesigning-identity, Code-sign the macOS application bundle and disk image while packaging, with this codesign identity ("-" for ad-hoc). Unset: package unsigned, as before. + Traditional macOS language packs cannot be installed + into a signed application because that would invalidate + its resource seal. Usage: --with-macosx-codesigning-identity="Developer ID Application: ..." ],with_macosx_codesigning_identity=$withval,) @@ -8294,4 +8297,3 @@ else echo fi - diff --git a/main/extensions/source/macosx/spotlight/makefile.mk b/main/extensions/source/macosx/spotlight/makefile.mk index 62a42394a2..3df817d3ef 100644 --- a/main/extensions/source/macosx/spotlight/makefile.mk +++ b/main/extensions/source/macosx/spotlight/makefile.mk @@ -85,7 +85,7 @@ $(MACOS)$/OOoSpotlightImporter: $(SLOFILES) $(CC) -o $(MACOS)$/OOoSpotlightImporter $(SLOFILES:s/.obj/.o/) $(EXTRA_LINKFLAGS) $(BUNDLELIBS) -bundle # we have to change the zlib install name, otherwise the plugin will not work .IF "$(SYSTEM_ZLIB)"=="NO" - install_name_tool -change @executable_path/libz.1.dylib @executable_path/../../../../MacOS/libz.1.dylib $(MACOS)$/OOoSpotlightImporter + install_name_tool -change @executable_path/libz.1.dylib @loader_path/../../../../../program/libz.1.dylib $(MACOS)$/OOoSpotlightImporter .ENDIF #the resources are just copied diff --git a/main/python/makefile.mk b/main/python/makefile.mk index de800af8f9..d4e2a038c7 100644 --- a/main/python/makefile.mk +++ b/main/python/makefile.mk @@ -125,6 +125,14 @@ BUILD_ACTION=$(ENV_BUILD) $(GNUMAKE) -j$(EXTMAXPROCESS) && $(GNUMAKE) install && BUILD_ACTION+= && for d in $(MYCWD)/python-inst/lib/python3.11 $(MYCWD)/python-inst/lib64/python3.11; do test -d "$$d" && (cd "$$d" && for f in _sysconfigdata_*.py; do test -f "$$f" && mv -f "$$f" _sysconfigdata.py; done); done; true .ENDIF +.IF "$(OS)"=="MACOSX" +# CPython records its temporary installation prefix in the interpreter's load +# command and the dylib ID. Both files are packaged together in program/. +BUILD_ACTION+= && (cd $(MYCWD)/python-inst/lib/python3.11 && for f in _sysconfigdata_*.py; do test -f "$$f" && cp -f "$$f" _sysconfigdata.py; done) +BUILD_ACTION+= && install_name_tool -id @loader_path/libpython3.11.dylib $(MYCWD)/python-inst/lib/libpython3.11.dylib +BUILD_ACTION+= && install_name_tool -change $(MYCWD)/python-inst/lib/libpython3.11.dylib @executable_path/libpython3.11.dylib $(MYCWD)/python-inst/bin/python3.11 +.ENDIF + .ELSE # ---------------------------------- # WINDOWS diff --git a/main/python/prj/d.lst b/main/python/prj/d.lst index 615f800cdc..09a6045ae2 100644 --- a/main/python/prj/d.lst +++ b/main/python/prj/d.lst @@ -98,12 +98,15 @@ mkdir: %_DEST%\inc%_EXT%\python\cpython ..\%__SRC%\misc\build\Python-3.11.15\Makefile %_DEST%\lib%_EXT%\python\python3.11\config\Makefile # _sysconfigdata filename is platform-mangled (e.g. _sysconfigdata__darwin_darwin.py on macOS, # _sysconfigdata__linux_x86_64-linux-gnu.py on Linux). On Linux, makefile.mk's BUILD_ACTION -# renames the generated file to the plain name below before delivery runs (not yet handled -# for other platforms). The source below is deliberately NOT a wildcard: deliver.pl's +# renames the generated file to the plain name below before delivery runs. The source below +# is deliberately NOT a wildcard: deliver.pl's # glob_line() only renames-on-copy when the destination also has a wildcard; paired with a # literal destination, a wildcard source makes it treat the destination as a directory and # append the matched filename onto it, producing a bogus nested path. ..\%__SRC%\misc\build\python-inst\lib\python3.11\_sysconfigdata.py %_DEST%\lib%_EXT%\python\_sysconfigdata.py +# Preserve the platform-mangled module that Python's sysconfig imports. Linux +# renames it above, so this wildcard is effective only where one remains. +..\%__SRC%\misc\build\python-inst\lib\python3.11\_sysconfigdata_*.py %_DEST%\lib%_EXT%\python\*.py ..\pyversion.mk %_DEST%\inc%_EXT%\pyversion.mk ..\pyversion_dmake.mk %_DEST%\inc%_EXT%\pyversion_dmake.mk diff --git a/main/setup_native/scripts/mac_install.script b/main/setup_native/scripts/mac_install.script index 842b51a9a9..0fc219d9a1 100644 --- a/main/setup_native/scripts/mac_install.script +++ b/main/setup_native/scripts/mac_install.script @@ -27,4 +27,4 @@ MY_DIR=$(dirname "$0") -osascript "$MY_DIR/osx_install.applescript" +osascript "$MY_DIR/../osx_install.applescript" diff --git a/main/setup_native/scripts/osx_install_languagepack.applescript b/main/setup_native/scripts/osx_install_languagepack.applescript index 96142a07ea..17b73a8d8f 100644 --- a/main/setup_native/scripts/osx_install_languagepack.applescript +++ b/main/setup_native/scripts/osx_install_languagepack.applescript @@ -53,6 +53,7 @@ set IdentifyQ to "[IdentifyQText] set IdentifyYES to "[IdentifyYES]" set IdentifyNO to "[IdentifyNO]" set installFailed to "[InstallFailedText]" +set installSignedFailed to "[InstallSignedFailedText]" set installComplete to "[InstallCompleteText] [InstallCompleteText2]" @@ -129,6 +130,16 @@ on error return 3 --wrong target-directory end try +-- Adding files below Contents invalidates a sealed application's resource +-- envelope. Refuse rather than silently break a Developer ID or ad-hoc seal. +try + do shell script "/usr/bin/codesign --display " & quoted form of (choice as string) + display dialog installSignedFailed buttons {OKLabel} default button 1 with icon 0 + return 4 +on error + -- Unsigned legacy installations can still accept the traditional language pack. +end try + (* display dialog startInstall buttons {AbortLabel, InstallLabel} default button 2 diff --git a/main/setup_native/scripts/osx_install_patch.applescript b/main/setup_native/scripts/osx_install_patch.applescript index 1cbf008046..634db8ef23 100644 --- a/main/setup_native/scripts/osx_install_patch.applescript +++ b/main/setup_native/scripts/osx_install_patch.applescript @@ -53,6 +53,7 @@ set IdentifyQ to "[IdentifyQText] set IdentifyYES to "[IdentifyYES]" set IdentifyNO to "[IdentifyNO]" set installFailed to "[InstallFailedText]" +set installSignedFailed to "[InstallSignedFailedText]" set installComplete to "[InstallCompleteTextPatch]" set sourcedir to (do shell script "dirname " & quoted form of POSIX path of (path to of me)) @@ -127,6 +128,15 @@ on error return 3 --wrong target-directory end try +-- A patch changes sealed bundle contents and would invalidate the application. +try + do shell script "/usr/bin/codesign --display " & quoted form of (choice as string) + display dialog installSignedFailed buttons {OKLabel} default button 1 with icon 0 + return 4 +on error + -- Preserve support for unsigned legacy installations. +end try + (* display dialog startInstall buttons {AbortLabel, InstallLabel} default button 2 diff --git a/main/setup_native/source/mac/macinstall.ulf b/main/setup_native/source/mac/macinstall.ulf index 58b09b5fb9..a1997f7a96 100644 --- a/main/setup_native/source/mac/macinstall.ulf +++ b/main/setup_native/source/mac/macinstall.ulf @@ -58,6 +58,9 @@ en-US = "No, abort installation" [InstallFailedText] en-US = "Installation failed." +[InstallSignedFailedText] +en-US = "This add-on cannot modify a signed [PRODUCTNAME] application. Install a complete language-specific application instead." + [InstallCompleteText] en-US = "Installation of [PRODUCTNAME] language pack completed." diff --git a/main/solenv/bin/macosx-codesign.sh b/main/solenv/bin/macosx-codesign.sh index 57d7762944..e04a973532 100755 --- a/main/solenv/bin/macosx-codesign.sh +++ b/main/solenv/bin/macosx-codesign.sh @@ -35,9 +35,15 @@ TARGETS=() while [ $# -gt 0 ]; do case "$1" in - -i|--identity) IDENTITY="$2"; shift 2 ;; - -e|--entitlements) ENTITLEMENTS="$2"; shift 2 ;; - -k|--keychain) KEYCHAIN="$2"; shift 2 ;; + -i|--identity) + [ $# -ge 2 ] || { echo "$1 requires an argument" >&2; exit 2; } + IDENTITY="$2"; shift 2 ;; + -e|--entitlements) + [ $# -ge 2 ] || { echo "$1 requires an argument" >&2; exit 2; } + ENTITLEMENTS="$2"; shift 2 ;; + -k|--keychain) + [ $# -ge 2 ] || { echo "$1 requires an argument" >&2; exit 2; } + KEYCHAIN="$2"; shift 2 ;; --hardened) HARDENED=yes; shift ;; --verify) VERIFY_ONLY=yes; shift ;; -h|--help) sed -n '2,25p' "$0"; exit 0 ;; @@ -55,26 +61,43 @@ sign_one() { if [ "$IDENTITY" != "-" ]; then args=(--force --sign "$IDENTITY" --timestamp) fi + if [ -n "$KEYCHAIN" ]; then + args+=(--keychain "$KEYCHAIN") + fi + codesign "${args[@]}" "$@" "$path" +} + +sign_executable() { + local path="$1"; shift if [ "$HARDENED" = yes ]; then - args+=(--options runtime --entitlements "$ENTITLEMENTS") + sign_one "$path" --options runtime --entitlements "$ENTITLEMENTS" "$@" + else + sign_one "$path" "$@" fi +} + +sign_disk_image() { + local path="$1" + local args=(--force --sign "$IDENTITY" --timestamp) if [ -n "$KEYCHAIN" ]; then args+=(--keychain "$KEYCHAIN") fi - codesign "${args[@]}" "$@" "$path" + codesign "${args[@]}" "$path" } report() { - local app="$1" - echo "--- $app" - codesign -dv --verbose=2 "$app" 2>&1 | grep -E 'Identifier|Format|CodeDirectory|Authority|TeamIdentifier|Sealed' || true - if codesign --verify --deep --strict "$app" 2>/dev/null; then - echo "verify: OK" - else - echo "verify: FAILED" - codesign --verify --deep --strict --verbose=2 "$app" 2>&1 | tail -5 + local target="$1" + echo "--- $target" + codesign -dv --verbose=2 "$target" 2>&1 | grep -E 'Identifier|Format|CodeDirectory|Authority|TeamIdentifier|Sealed' || true + if ! codesign --verify --deep --strict --verbose=2 "$target"; then + echo "verify: FAILED" >&2 + return 1 fi - spctl --assess --type exec --verbose=4 "$app" 2>&1 | tail -2 || true + echo "verify: OK" + case "$target" in + *.dmg) spctl --assess --type open --context context:primary-signature --verbose=4 "$target" || true ;; + *) spctl --assess --type exec --verbose=4 "$target" || true ;; + esac } # codesign rewrites every Mach-O it signs and writes _CodeSignature/ into @@ -90,6 +113,10 @@ open_for_signing() { sign_app() { local app="$1" + [ -d "$app/Contents" ] && [ -f "$app/Contents/Info.plist" ] || { + echo "not an application bundle: $app" >&2 + return 1 + } echo "==> signing $app (identity: $IDENTITY, hardened: $HARDENED)" # Quarantine and other xattrs make codesign fail or produce an unstable seal. @@ -109,10 +136,25 @@ sign_app() { # Find every Mach-O with one batched file(1) run instead of a process per # file (an installation holds ~10k). --print0 emits "path\0: type\n". - local machos=() f type + local machos=() executable_machos=() f type while IFS= read -r -d '' f && IFS= read -r type; do - case "$type" in ": Mach-O"*) machos+=("$f") ;; esac + case "$type" in + ": Mach-O"*) + machos+=("$f") + case "$type" in *" executable"*) executable_machos+=("$f") ;; esac + ;; + esac done < <(find "$app" -type f -print0 | xargs -0 file --no-pad --print0 -- 2>/dev/null) + if [ ${#machos[@]} -gt 0 ]; then + local bad_load_commands + bad_load_commands=$(printf '%s\0' "${machos[@]}" | xargs -0 otool -L 2>/dev/null | + grep -E 'python-inst|@_______' || true) + if [ -n "$bad_load_commands" ]; then + echo "unrelocated Mach-O load commands in $app:" >&2 + echo "$bad_load_commands" >&2 + return 1 + fi + fi # codesign rewrites a Mach-O through a temporary file beside it, so the # containing directory has to be writable as well. if [ ${#machos[@]} -gt 0 ]; then @@ -123,12 +165,16 @@ sign_app() { fi for b in ${bundles[@]+"${bundles[@]}"}; do open_for_signing "$b"; done - # 1. every Mach-O object, deepest path first + # 1. every Mach-O object, deepest path first. Process entitlements belong + # on executables; dylibs and plug-ins inherit the host process's policy. local count=0 if [ ${#machos[@]} -gt 0 ]; then while IFS= read -r f; do case " ${main_execs[*]-} " in *" $f "*) continue ;; esac - sign_one "$f" + case " ${executable_machos[*]-} " in + *" $f "*) sign_executable "$f" ;; + *) sign_one "$f" ;; + esac count=$((count + 1)) done < <(printf '%s\n' "${machos[@]}" | awk '{ print gsub(/\//,"/") "\t" $0 }' | sort -rn | cut -f2-) fi @@ -137,12 +183,15 @@ sign_app() { # 2. nested bundles, deepest first, so each seal covers already-signed contents for b in ${bundles[@]+"${bundles[@]}"}; do [ "$b" = "$app" ] && continue - sign_one "$b" + case "$b" in + *.app) sign_executable "$b" ;; + *) sign_one "$b" ;; + esac echo " sealed nested bundle: ${b#"$app"/}" done # 3. the app bundle itself - sign_one "$app" + sign_executable "$app" echo " sealed $app" report "$app" } @@ -157,7 +206,7 @@ for target in "${TARGETS[@]}"; do echo "refusing to ad-hoc sign a .dmg (pointless); pass -i <Developer ID>" >&2 exit 1 fi - codesign --force --sign "$IDENTITY" --timestamp "$target" + sign_disk_image "$target" report "$target" ;; *) diff --git a/main/solenv/bin/modules/installer/simplepackage.pm b/main/solenv/bin/modules/installer/simplepackage.pm index 4e94abf12b..eb0a342425 100644 --- a/main/solenv/bin/modules/installer/simplepackage.pm +++ b/main/solenv/bin/modules/installer/simplepackage.pm @@ -34,6 +34,13 @@ use installer::strip; use installer::systemactions; use installer::worker; +sub shell_quote +{ + my ( $value ) = @_; + $value =~ s/'/'"'"'/g; + return "'$value'"; +} + #################################################### # Checking if the simple packager is required. # This can be achieved by setting the global @@ -496,7 +503,9 @@ sub create_package if ($$translationfileref eq "") { installer::exiter::exit_program("ERROR: Could not find Apple script translation file $translationfilename!", "create_package"); } $scriptfilename = $contentsfolder . "/" . $scriptrealfilename; - $scripthelperrealfilename = $contentsfolder . "/" . $scripthelperrealfilename; + my $macosfolder = $contentsfolder . "/MacOS"; + installer::systemactions::create_directory($macosfolder); + $scripthelperrealfilename = $macosfolder . "/" . $scripthelperrealfilename; installer::systemactions::copy_one_file($$scriptref, $scriptfilename); installer::systemactions::copy_one_file($$scripthelperref, $scripthelperrealfilename); @@ -548,8 +557,10 @@ sub create_package my $signscript = $ENV{'SOLARENV'} . "/bin/macosx-codesign.sh"; foreach my $appdir ( glob("$localtempdir/$folder/*.app") ) { - my $signcall = "$signscript -i \"$ENV{'MACOSX_CODESIGNING_IDENTITY'}\" \"$appdir\""; - my $signreturn = system($signcall); + my @signcall = ($signscript, "-i", $ENV{'MACOSX_CODESIGNING_IDENTITY'}); + push(@signcall, "-k", $ENV{'MACOSX_CODESIGNING_KEYCHAIN'}) if $ENV{'MACOSX_CODESIGNING_KEYCHAIN'}; + push(@signcall, $appdir); + my $signreturn = system(@signcall); if ( $signreturn ) { installer::exiter::exit_program("ERROR: Could not code-sign $appdir!", "create_package"); } $installer::logger::Lang->print("Success: Code-signed $appdir\n"); } @@ -564,9 +575,14 @@ sub create_package # "-fs HFS+" is not optional: left to itself "hdiutil create" makes an # APFS image on recent macOS, which will not mount before 10.12. # "makehybrid -hfs" always produced HFS+. - $systemcall = "cd $localtempdir && hdiutil create -srcfolder $folder -volname \"$volume_name\" -fs HFS+ -format UDZO -ov $archive"; + $systemcall = "cd " . shell_quote($localtempdir) . + " && hdiutil create -srcfolder " . shell_quote($folder) . + " -volname " . shell_quote($volume_name) . + " -fs HFS+ -format UDZO -ov " . shell_quote($archive); if (( $ref ne "" ) && ( $$ref ne "" )) { - $systemcall .= " && hdiutil unflatten $archive && Rez -a $$ref -o $archive && hdiutil flatten $archive"; + $systemcall .= " && hdiutil unflatten " . shell_quote($archive) . + " && Rez -a " . shell_quote($$ref) . " -o " . shell_quote($archive) . + " && hdiutil flatten " . shell_quote($archive); } } else @@ -617,8 +633,10 @@ sub create_package ( $ENV{'MACOSX_CODESIGNING_IDENTITY'} ne "-" )) { my $signscript = $ENV{'SOLARENV'} . "/bin/macosx-codesign.sh"; - my $signcall = "$signscript -i \"$ENV{'MACOSX_CODESIGNING_IDENTITY'}\" \"$archive\""; - my $signreturn = system($signcall); + my @signcall = ($signscript, "-i", $ENV{'MACOSX_CODESIGNING_IDENTITY'}); + push(@signcall, "-k", $ENV{'MACOSX_CODESIGNING_KEYCHAIN'}) if $ENV{'MACOSX_CODESIGNING_KEYCHAIN'}; + push(@signcall, $archive); + my $signreturn = system(@signcall); if ( $signreturn ) { installer::exiter::exit_program("ERROR: Could not code-sign $archive!", "create_package"); } $installer::logger::Lang->print("Success: Code-signed $archive\n"); }
