This is an automated email from the ASF dual-hosted git repository.

jimjag pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/openoffice.git


The following commit(s) were added to refs/heads/trunk by this push:
     new d884924344 macOS: harden application and disk image signing
d884924344 is described below

commit d884924344e6b15164cf98f20d9dbdded7540356
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.
---
 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 cc214ad613..8f10f97b16 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,)
@@ -8281,4 +8284,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");
                        }

Reply via email to