From c3333edc771c1d101fe89e855a628fec7b439274 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Tue, 2 Nov 2021 11:27:33 -0700 Subject: [PATCH 1/5] Fix cpu and friendly model for apple silicon --- .../sal/checkin_modules/machine_checkin.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/payload/usr/local/sal/checkin_modules/machine_checkin.py b/payload/usr/local/sal/checkin_modules/machine_checkin.py index 355310e..1b75521 100755 --- a/payload/usr/local/sal/checkin_modules/machine_checkin.py +++ b/payload/usr/local/sal/checkin_modules/machine_checkin.py @@ -53,9 +53,14 @@ def process_system_profile(): friendly_model = get_friendly_model(machine_results["serial"]) if friendly_model: machine_results["machine_model_friendly"] = friendly_model - machine_results["cpu_type"] = system_profile["SPHardwareDataType"][0].get( - "cpu_type", "" - ) + if system_profile["SPHardwareDataType"][0].get("chip_type", None): + machine_results["cpu_type"] = system_profile["SPHardwareDataType"][0].get( + "chip_type", "" + ) + else: + machine_results["cpu_type"] = system_profile["SPHardwareDataType"][0].get( + "cpu_type", "" + ) machine_results["cpu_speed"] = system_profile["SPHardwareDataType"][0][ "current_processor_speed" ] @@ -100,6 +105,19 @@ def get_machine_name(net_config, nametype): def get_friendly_model(serial): """Return friendly model name""" + cmd = ["/usr/sbin/ioreg", "-arc", "IOPlatformDevice", "-k", "product-name"] + try: + out = subprocess.check_output(cmd) + except: + pass + if out: + try: + data = plistlib.loads(out) + if len(data) != 0: + return data[0].get("product-name").decode("utf-8") + except: + pass + if not MODEL_PATH.exists(): model = cleanup_model(query_apple_support(serial)) if model: From 3f533b7486018039872c6d4f8395c0ae04f1b8f2 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Tue, 2 Nov 2021 11:28:38 -0700 Subject: [PATCH 2/5] Fix vuln --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 55b1cfa..b946464 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ sal_python_pkg/ pyobjc==6.2.2 -urllib3==1.25.10 +urllib3==1.26.5 requests==2.24.0 -MacSesh==0.3.0 +MacSesh==0.3.0 \ No newline at end of file From e9adac5eeff6201eda1874ae74e3ae7087a76e33 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Thu, 11 Nov 2021 20:16:46 -0800 Subject: [PATCH 3/5] Apple silicon compatible version --- .gitignore | 4 +- Makefile | 25 +++- build_python_framework.sh | 30 ++++- requirements.txt | 10 +- sal_python_pkg/sal/version.py | 2 +- sal_python_pkg/setup.py | 2 +- sign_python_framework.py | 237 ++++++++++++++++++++++++++++++++++ 7 files changed, 299 insertions(+), 11 deletions(-) create mode 100755 sign_python_framework.py diff --git a/.gitignore b/.gitignore index dd2d01b..db27da5 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,6 @@ report_broken_client/report_broken_client.xcodeproj/xcuserdata/* vendor/ report_broken_client/build -.vscode/ \ No newline at end of file +.vscode/ +config.mk +entitlements.plist \ No newline at end of file diff --git a/Makefile b/Makefile index 99d5bf5..f8f052d 100755 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ USE_PKGBUILD=1 include luggage/luggage.make +include config.mk PACKAGE_VERSION:=$(shell sed -n -e '/^__version__/p' sal_python_pkg/sal/version.py | cut -d "\"" -f 2) +PB_EXTRA_ARGS+= --sign "${DEV_INSTALL_CERT}" +.PHONY: remove-xattrs sign clean-build TITLE=sal_scripts PACKAGE_NAME=sal_scripts REVERSE_DOMAIN=com.github.salopensource @@ -11,9 +14,12 @@ PAYLOAD=\ pack-Library-LaunchDaemons-com.salopensource.sal.runner.plist \ pack-Library-LaunchDaemons-com.salopensource.sal.random.runner.plist \ pack-script-postinstall \ - pack-python + pack-python \ + remove-xattrs \ + sign clean-build: + killall Dropbox || true @sudo rm -rf report_broken_client/build pack-report-broken-client: pack-sal-scripts clean-build @@ -46,4 +52,19 @@ build-python: @rm -rf "${PYTHONTOOLDIR}" @git clone https://github.com/gregneagle/relocatable-python.git "${PYTHONTOOLDIR}" @./build_python_framework.sh - @find ./Python.framework -name '*.pyc' -delete \ No newline at end of file + @find ./Python.framework -name '*.pyc' -delete + +sign: remove-xattrs + @sudo ./sign_python_framework.py -v -S "${DEV_APP_CERT}" + +remove-xattrs: + @sudo xattr -rd com.dropbox.attributes ${WORK_D} + @sudo xattr -rd com.dropbox.internal ${WORK_D} + @sudo xattr -rd com.apple.ResourceFork ${WORK_D} + @sudo xattr -rd com.apple.FinderInfo ${WORK_D} + @sudo xattr -rd com.apple.metadata:_kMDItemUserTags ${WORK_D} + @sudo xattr -rd com.apple.metadata:kMDItemFinderComment ${WORK_D} + @sudo xattr -rd com.apple.metadata:kMDItemOMUserTagTime ${WORK_D} + @sudo xattr -rd com.apple.metadata:kMDItemOMUserTags ${WORK_D} + @sudo xattr -rd com.apple.metadata:kMDItemStarRating ${WORK_D} + @sudo xattr -rd com.dropbox.ignored ${WORK_D} \ No newline at end of file diff --git a/build_python_framework.sh b/build_python_framework.sh index 68ebe03..81afd84 100755 --- a/build_python_framework.sh +++ b/build_python_framework.sh @@ -1,10 +1,36 @@ #!/bin/zsh # Build script for Python 3 framework for Sal scripts TOOLSDIR=$(dirname "$0") -PYTHON_VERSION=3.8.5 +PYTHON_VERSION=3.9.7 # build the framework /tmp/relocatable-python-git/make_relocatable_python_framework.py \ --python-version "${PYTHON_VERSION}" \ --pip-requirements requirements.txt \ - --destination "${TOOLSDIR}" + --destination "${TOOLSDIR}" \ + --os-version "11" \ + --upgrade-pip + +# Stolen with love from https://github.com/macadmins/python/blob/main/build_python_framework_pkgs.zsh +TOTAL_DYLIB=$(/usr/bin/find "${TOOLSDIR}/Python.framework/Versions/Current/lib" -name "*.dylib" | /usr/bin/wc -l | /usr/bin/xargs) +UNIVERSAL_DYLIB=$(/usr/bin/find "${TOOLSDIR}/Python.framework/Versions/Current/lib" -name "*.dylib" | /usr/bin/xargs file | /usr/bin/grep "2 architectures" | /usr/bin/wc -l | /usr/bin/xargs) +if [ "${TOTAL_DYLIB}" != "${UNIVERSAL_DYLIB}" ] ; then + echo "Dynamic Libraries do not match, resulting in a non-universal Python framework." + echo "Total Dynamic Libraries found: ${TOTAL_DYLIB}" + echo "Universal Dynamic Libraries found: ${UNIVERSAL_DYLIB}" + exit 1 +fi + +echo "Dynamic Libraries are confirmed as universal" + +TOTAL_SO=$(/usr/bin/find "${TOOLSDIR}/Python.framework/Versions/Current/lib" -name "*.so" | /usr/bin/wc -l | /usr/bin/xargs) +UNIVERSAL_SO=$(/usr/bin/find "${TOOLSDIR}/Python.framework/Versions/Current/lib" -name "*.so" | /usr/bin/xargs file | /usr/bin/grep "2 architectures" | /usr/bin/wc -l | /usr/bin/xargs) +if [ "${TOTAL_SO}" != "${UNIVERSAL_SO}" ] ; then + echo "Shared objects do not match, resulting in a non-universal Python framework." + echo "Total shared objects found: ${TOTAL_SO}" + echo "Universal shared objects found: ${UNIVERSAL_SO}" + UNIVERSAL_SO_ARRAY=("${(@f)$(/usr/bin/find "${TOOLSDIR}/Python.framework/Versions/Current/lib" -name "*.so" | /usr/bin/xargs file | /usr/bin/grep "2 architectures" | awk '{print $1;}' | sed 's/:*$//g')}") + TOTAL_SO_ARRAY=("${(@f)$(/usr/bin/find "${TOOLSDIR}/Python.framework/Versions/Current/lib" -name "*.so" )}") + echo ${TOTAL_SO_ARRAY[@]} ${UNIVERSAL_SO_ARRAY[@]} | tr ' ' '\n' | sort | uniq -u + exit 1 +fi \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b946464..9af859b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ sal_python_pkg/ -pyobjc==6.2.2 -urllib3==1.26.5 -requests==2.24.0 -MacSesh==0.3.0 \ No newline at end of file +pyobjc==7.2 +#urllib3==1.26.5 +requests==2.26.0 +MacSesh==0.3.0 +six==1.16.0 +--no-binary :all: \ No newline at end of file diff --git a/sal_python_pkg/sal/version.py b/sal_python_pkg/sal/version.py index 72aa758..0fd7811 100644 --- a/sal_python_pkg/sal/version.py +++ b/sal_python_pkg/sal/version.py @@ -1 +1 @@ -__version__ = "4.1.1" +__version__ = "4.2.0" diff --git a/sal_python_pkg/setup.py b/sal_python_pkg/setup.py index bd73647..61caf9b 100644 --- a/sal_python_pkg/setup.py +++ b/sal_python_pkg/setup.py @@ -11,7 +11,7 @@ version=namespace["__version__"], description="Sal client utilities", install_requires=[ - 'pyobjc == 6.2.2 ; platform_system=="Darwin"', + 'pyobjc >= 6.2.2 ; platform_system=="Darwin"', 'macsesh == 0.3.0 ; platform_system=="Darwin"', "requests >= 2.23.0", ], diff --git a/sign_python_framework.py b/sign_python_framework.py new file mode 100755 index 0000000..aac2219 --- /dev/null +++ b/sign_python_framework.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 + +# encoding: utf-8 +""" +Based on https://github.com/ox-it/munki-rebrand + + +Original license is below + +Copyright (C) University of Oxford 2016-21 + Ben Goodstein + +Based on an original script by Arjen van Bochoven + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +import subprocess +import os +import stat +import shutil +from tempfile import mkdtemp +import plistlib +import argparse +import sys +import atexit + +PYTHON_VERSION = "3.9.7" +SHORT_PYTHON_VERSION = "3.9" +TOOLS_DIR = os.path.dirname(os.path.realpath(__file__)) + + +PY_FWK = os.path.join(TOOLS_DIR, "Python.Framework") +PY_CUR = os.path.join(PY_FWK, "Versions/Current") + + +PRODUCTSIGN = "/usr/bin/productsign" +CODESIGN = "/usr/bin/codesign" + +global verbose +verbose = False +tmp_dir = mkdtemp() + + +@atexit.register +def cleanup(): + print("Cleaning up...") + try: + shutil.rmtree(tmp_dir) + # In case subprocess cleans up before we do + except OSError: + pass + print("Done.") + + +def run_cmd(cmd, ret=None): + """Runs a command passed in as a list. Can also be provided with a regex + to search for in the output, returning the result""" + if verbose: + print(f"Running command {cmd}") + proc = subprocess.run(cmd, capture_output=True) + if verbose and proc.stdout != b"" and not ret: + print(proc.stdout.rstrip().decode()) + if proc.returncode != 0: + print(proc.stderr.rstrip().decode()) + sys.exit(1) + if ret: + return proc.stdout.rstrip().decode() + + +def sign_package(signing_id, pkg): + """Signs a pkg with a signing id""" + cmd = [PRODUCTSIGN, "--sign", signing_id, pkg, f"{pkg}-signed"] + print("Signing pkg...") + run_cmd(cmd) + print(f"Moving {pkg}-signed to {pkg}...") + os.rename(f"{pkg}-signed", pkg) + + +def sign_binary( + signing_id, + binary, + verbose=False, + deep=False, + options=[], + entitlements="", + force=False, +): + """Signs a binary with a signing id, with optional arguments for command line + args""" + cmd = [CODESIGN, "--timestamp", "--sign", signing_id] + if force: + cmd.append("--force") + if deep: + cmd.append("--deep") + if verbose: + cmd.append("--verbose") + if entitlements: + cmd.append("--entitlements") + cmd.append(entitlements) + if options: + cmd.append("--options") + cmd.append(",".join([option for option in options])) + cmd.append(binary) + run_cmd(cmd) + + +def is_signable_bin(path): + """Checks if a path is a file and is executable""" + if os.path.isfile(path) and (os.stat(path).st_mode & stat.S_IXUSR > 0): + return True + return False + + +def is_signable_lib(path): + """Checks if a path is a file and ends with .so or .dylib""" + if os.path.isfile(path) and (path.endswith(".so") or path.endswith(".dylib")): + return True + return False + + +def main(): + p = argparse.ArgumentParser(description="Builds and signs Python") + + p.add_argument( + "-S", + "--sign-binaries", + action="store", + default=None, + help="A Developer ID Application certificate from keychain. " + "Provide the certificate's Common Name. e.g.: " + "'Developer ID Application Munki (U8PN57A5N2)'", + ), + p.add_argument("-v", "--verbose", action="store_true", help="Be more verbose"), + + args = p.parse_args() + + if os.geteuid() != 0: + print( + "You must run this script as root in order to build your new " + "Python installer pkg!" + ) + sys.exit(1) + + if not args.sign_binaries: + print("You must specify a signing identity") + sys.exit(1) + + global verbose + verbose = args.verbose + + root_dir = os.path.join(TOOLS_DIR, "Python.framework") + # Set root:admin throughout payload + for root, dirs, files in os.walk(root_dir): + for dir_ in dirs: + os.chown(os.path.join(root, dir_), 0, 80) + for file_ in files: + os.chown(os.path.join(root, file_), 0, 80) + + # Generate entitlements file for later + entitlements = { + "com.apple.security.cs.allow-unsigned-executable-memory": True, + "com.apple.security.cs.allow-jit": True, + "com.apple.security.cs.allow-dyld-environment-variables": True, + "com.apple.security.cs.disable-library-validation": True, + } + + ent_file = os.path.join(tmp_dir, "entitlements.plist") + with open(ent_file, "wb") as f: + plistlib.dump(entitlements, f) + + binaries = [] + # Add the executable libs and bins in python pkg + pylib = os.path.join(root_dir, PY_CUR, "lib") + pybin = os.path.join(root_dir, PY_CUR, "bin") + for pydir in pylib, pybin: + binaries.extend( + [ + os.path.join(pydir, f) + for f in os.listdir(pydir) + if is_signable_bin(os.path.join(pydir, f)) + ] + ) + for root, dirs, files in os.walk(pydir): + for file_ in files: + if is_signable_lib(os.path.join(root, file_)): + binaries.append(os.path.join(root, file_)) + + # Add binaries which need entitlements + entitled_binaries = [ + os.path.join(root_dir, PY_CUR, "Resources/Python.app"), + os.path.join(pybin, "python3"), + ] + + # Sign all the binaries. The order is important. Which is why this is a bit + # gross + print("Signing binaries (this may take a while)...") + for binary in binaries: + if verbose: + print(f"Signing {binary}...") + sign_binary( + args.sign_binaries, + binary, + deep=True, + force=True, + options=["runtime"], + ) + for binary in entitled_binaries: + if verbose: + print(f"Signing {binary} with entitlements from {ent_file}...") + sign_binary( + args.sign_binaries, + binary, + deep=True, + force=True, + options=["runtime"], + entitlements=ent_file, + ) + # Finally sign python framework + py_fwkpath = os.path.join(root_dir, PY_FWK) + if verbose: + print(f"Signing {py_fwkpath}...") + sign_binary(args.sign_binaries, py_fwkpath, deep=True, force=True) + + +if __name__ == "__main__": + main() \ No newline at end of file From 4cf136bb7fab55af3a1d7e3e40577f3d099071ec Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Thu, 11 Nov 2021 20:21:38 -0800 Subject: [PATCH 4/5] Work out how to build the thing later --- .github/workflows/build_pkg.yaml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build_pkg.yaml b/.github/workflows/build_pkg.yaml index 3982499..3611d92 100644 --- a/.github/workflows/build_pkg.yaml +++ b/.github/workflows/build_pkg.yaml @@ -1,18 +1,18 @@ -name: Build Package +# name: Build Package -on: push +# on: push -jobs: - build_pkg: - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - # - name: Install Luggage - # run: | - # mkdir -p /usr/local/share/luggage - # cp luggage/luggage.make /usr/local/share/luggage/luggage.make - # cp luggage/prototype.plist /usr/local/share/luggage/prototype.plist - - name: make pkg - run: | - sudo make pkg +# jobs: +# build_pkg: +# runs-on: macos-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v1 +# # - name: Install Luggage +# # run: | +# # mkdir -p /usr/local/share/luggage +# # cp luggage/luggage.make /usr/local/share/luggage/luggage.make +# # cp luggage/prototype.plist /usr/local/share/luggage/prototype.plist +# - name: make pkg +# run: | +# sudo make pkg From 6fb6280886d73e7593ca480d5969231bb3e2391c Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Thu, 11 Nov 2021 20:22:02 -0800 Subject: [PATCH 5/5] Black --- payload/usr/local/sal/checkin_modules/machine_checkin.py | 4 ++-- sign_python_framework.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/payload/usr/local/sal/checkin_modules/machine_checkin.py b/payload/usr/local/sal/checkin_modules/machine_checkin.py index 1d41df8..27634b7 100755 --- a/payload/usr/local/sal/checkin_modules/machine_checkin.py +++ b/payload/usr/local/sal/checkin_modules/machine_checkin.py @@ -51,7 +51,7 @@ def process_system_profile(): "machine_model" ] - udid = system_profile["SPHardwareDataType"][0]['provisioning_UDID'] + udid = system_profile["SPHardwareDataType"][0]["provisioning_UDID"] friendly_model = get_friendly_model(serial=machine_results["serial"], udid=udid) if friendly_model: machine_results["machine_model_friendly"] = friendly_model @@ -124,7 +124,7 @@ def get_friendly_model(serial, udid): MODEL_PATH.mkdir(mode=0o755, parents=True, exist_ok=True) # name cache for this udid - UDID_CACHE_PATH = pathlib.Path(MODEL_PATH, '%s.txt' % (udid)) + UDID_CACHE_PATH = pathlib.Path(MODEL_PATH, "%s.txt" % (udid)) for cache_file in MODEL_PATH.iterdir(): # clean up any other files in dir if cache_file != UDID_CACHE_PATH: diff --git a/sign_python_framework.py b/sign_python_framework.py index aac2219..7725372 100755 --- a/sign_python_framework.py +++ b/sign_python_framework.py @@ -234,4 +234,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main()