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");
}