https://bugs.documentfoundation.org/show_bug.cgi?id=174182

            Bug ID: 174182
           Summary: macOS: all 1599 shipped .pyc files in
                    LibreOfficePython.framework are stale (26.8.1.1
                    ARM64); their first import rewrites sealed files and
                    invalidates the code signature
           Product: LibreOffice
           Version: 26.8.1.1 release
          Hardware: ARM
                OS: macOS (All)
            Status: UNCONFIRMED
          Severity: normal
          Priority: medium
         Component: Installation
          Assignee: [email protected]
          Reporter: [email protected]

Description:
All 1599 CPython bytecode files (`.pyc`) shipped inside
`LibreOffice.app/Contents/Frameworks/LibreOfficePython.framework/Versions/3.13`
in the official macOS ARM64 DMG of
LibreOffice 26.8.1.1 are invalid for their adjacent `.py` sources:
- they are timestamp-based `.pyc` (flags 0);
- the source mtime recorded in each header is 4632-4651 seconds older than the
mtime of the shipped `.py` file;
- the recorded source size matches in every case.

CPython therefore treats every shipped `.pyc` as stale and recompiles the
module on import. It **writes the
recompiled `.pyc` back into the bundle when two conditions hold**:
- the bundle is writable for the user running LibreOffice. That is the usual
case after a drag-and-drop install by
  an administrator account.
- bytecode writing is not disabled in LibreOffice's Python. It is enabled by
default: no `-B`, no
  `PYTHONDONTWRITEBYTECODE`, `sys.dont_write_bytecode` False, no
`pycache_prefix`.

In that case the first import of a module rewrites its sealed `.pyc` inside the
Developer ID-signed framework. That
invalidates the framework's sealed resources, and `codesign` and `spctl`
subsequently reject the app. On a
non-writable installation nothing is written; the stale files only cost a
recompilation on every import.

The `.pyc` files are themselves sealed in the framework's `CodeResources`: the
signed bundle contains bytecode that
CPython will not use.

## Affected build (verified)

| Item | Value |
|---|---|
| File | `LibreOffice_26.8.1.1_MacOS_aarch64.dmg` |
| URL |
https://downloadarchive.documentfoundation.org/libreoffice/old/26.8.1.1/mac/aarch64/LibreOffice_26.8.1.1_MacOS_aarch64.dmg
|
| Size | 298845477 bytes |
| SHA-256 | `ca074e0b13571efb30a31fc531c5f95e997030637325e5d5de92d67aefdf0dfc`
(matches the `.mirrorlist`; SHA-1 and MD5 also match) |
| PGP | `.asc` verifies: GOODSIG + VALIDSIG, primary key fingerprint
`C2839ECAD9408FBE9531C3E9F434A1EFAFEEAEA3` (LibreOffice Build Team code signing
key) |
| App | `CFBundleShortVersionString` / `CFBundleVersion` 26.8.1.1; signed
"Developer ID Application: The Document Foundation (7P5S3ZLCN7)", hardened
runtime |
| Embedded Python | CPython 3.13 (magic `f30d0d0a`); framework
`org.python.libreofficepython`, 3452 sealed files, 1599 of them `.pyc` |

## Verified facts

| # | Fact | Method |
|---|---|---|
| V1 | 1599 `.pyc` under `.../Versions/3.13/lib/python3.13`, 533 each of plain
/ `.opt-1` / `.opt-2`; none outside the framework | DMG extracted with 7-Zip
25.01 (HFS+). Completeness: SHA-256 of every sealed regular file matched
`CodeResources` (3438 framework files; the 14 framework symlink entries cannot
be materialized on Windows and were checked separately) |
| V2 | 1599/1599 are STALE_MTIME (flags 0, magic 3.13, size equal, mtime
differs); 0 valid, 0 hash-based, 0 without source | header check, attached
script (output below) |
| V3 | recorded source mtimes: 2026-09-17 13:55:01-13:55:19 UTC; actual `.py`
mtimes: 2026-09-17 15:12:31-15:12:32 UTC (two distinct values) | header fields
vs. file metadata from the HFS+ image |
| V4 | all 1599 `.pyc` are listed in the framework's `CodeResources` with
hashes equal to the shipped files | `files2` `hash2` comparison |
| V5 | the extracted mtimes equal those of a real installation of the same DMG
(`stat` on the Mac; 0 differences for framework `.py`/`.pyc`) | comparison
against the installed copy below |

Script output on the extracted DMG (run by us with CPython 3.12.10 on Windows):

```
pyc files found: 1599
  opt-1  STALE_MTIME                533
  opt-2  STALE_MTIME                533
  plain  STALE_MTIME                533
source_mtime - recorded_mtime (seconds: count): {4632: 3, 4645: 81, 4646: 183,
4647: 231, 4648: 237, 4649: 318, 4650: 462, 4651: 84}
```

## Observed on an installed copy (one machine, read-only measurement)

MacBook Air M2 (arm64), macOS 26.7.1 (build 25G241). The same 26.8.1.1 was
installed by drag-and-drop into
`/Applications`; the bundle is owned by the (admin) user and is writable. The
app had been used normally. Read-only
checks with `codesign`, `spctl`, `stat`, `shasum`:

- 120 framework `.pyc` differ from their sealed hashes ("file modified"); all
120 are among the stale shipped files.
  Their birth time equals their mtime: they were replaced, not edited.
- 6 new `.pyc` were added in `Contents/Resources` (`pythonloader`, `uno`,
`unohelper`, three `lightproof_*_en`).
  These are the same files as in tdf#133710 ("file added"), see below.
- `codesign --verify --strict` on the framework: "a sealed resource is missing
or invalid", listing exactly the 120.
- `spctl --assess --type execute`: **rejected** ("a sealed resource is missing
or invalid").
- LibreOffice still launched on that machine afterwards (already approved by
Gatekeeper). We did not test a fresh
  quarantined copy or another Mac.
- A stale shipped `.pyc` that had not yet been imported (`tabnanny`) still
matched its sealed hash and carried the
  same mtime mismatch (4650 s).

Timing of the 120 rewrites (mtime): 119 on the first days of Python use (Python
extensions under development were
being tested) and 1 later. We cannot attribute each rewrite to LibreOffice
itself vs. extensions. That does not
change the cause: any first import rewrites a stale file.


Steps to Reproduce:
A. Offline. **Verified by us** on the extracted DMG (Windows, CPython 3.12.10).
The macOS variant below was **not
executed** by us.
1. Mount the DMG; do not launch the app.
2. With any separate Python 3.7+ (e.g. from python.org; LibreOffice's embedded
interpreter cannot be started from
   Terminal, see tdf#167752):
   `python3 check_pyc_headers.py /Volumes/LibreOffice/LibreOffice.app` (script
attached; stdlib only, read-only).
3. Expected: all `.pyc` VALID. Actual (extracted DMG): 1599 STALE_MTIME.

B. On a fresh installation. Suggested; **not executed by us** on a pristine
install:
1. As an admin user, drag LibreOffice.app to /Applications. Before the first
launch, run
   `codesign --verify --strict -v
/Applications/LibreOffice.app/Contents/Frameworks/LibreOfficePython.framework`.
   Expected: valid.
2. Create `~/Library/Application
Support/LibreOffice/4/user/Scripts/python/stalepyc.py` with:
   ```python
   def run(*args):
       import tabnanny
   ```
3. Launch LibreOffice and run it via Tools > Macros > Run Macro... > My Macros
> stalepyc > run. The embedded Python
   runs in-process here, as intended by the launch constraint.
4. Quit LibreOffice and repeat the `codesign` command from step 1.
   Expected per V2 and the observations above:
   - "file modified" for `lib/python3.13/__pycache__/tabnanny.cpython-313.pyc`;
   - plus any other stdlib modules imported for the first time during the
session;
   - plus the "file added" entries of tdf#133710.


Actual Results:
Step A: Actual (extracted DMG): 1599 STALE_MTIME.
On the installed copy (see Description, read-only measurement on one machine):
- `codesign --verify --strict` on the framework: "a sealed resource is missing
or invalid", listing exactly the 120.
- `spctl --assess --type execute`: **rejected** ("a sealed resource is missing
or invalid").


Expected Results:
Step A: Expected: all `.pyc` VALID.
Step B: the framework signature stays valid after use (step B.1 before first
launch: `Expected: valid.`).



Reproducible: Always


User Profile Reset: No

Additional Info:
## Hypothesis (not verified)

The pattern (all sizes equal; every recorded mtime about 77 minutes older; all
`.py` files carrying one of two
adjacent timestamps) suggests the `.py` files received new mtimes after
`compileall` had run. For example, a copy step
during packaging that does not preserve timestamps. We have not looked at the
build scripts.

## Possible directions (not validated by us)

- Compile the bytecode after the final copy of the `.py` files, or preserve
source mtimes in every later copy step.
- Or compile with `--invalidation-mode checked-hash` (PEP 552). Such `.pyc`
stay valid regardless of mtimes.
  `unchecked-hash` is an option if sources are never changed.
- Independently of the above, avoid runtime writes into the signed bundle: e.g.
run the embedded interpreter with
  bytecode writing disabled, or with `sys.pycache_prefix` pointing to a
per-user cache. This would also cover the
  "file added" case of tdf#133710.

Each has trade-offs (start-up cost, cache location) that the maintainers are
better placed to judge.

## Related / duplicate search

Bugzilla was searched manually on 2026-10-27 at about 15:17 UTC: quicksearch
"pycache", "pyc macOS", and with all
statuses "pycache", "sealed resource", "LibreOfficePython", "compileall",
"notarization python". **No report about
stale shipped `.pyc` was found.**

Related:
- **tdf#133710** "Notarization gets corrupted after initial launch" (macOS, LO
7.x / Python 3.7), RESOLVED
  WORKSFORME on 2025-03-09. It concerns the *added* `__pycache__` files in
`Contents/Resources` (`uno`, `unohelper`,
  `pythonloader`, lightproof). We observe the same six files added again in
26.8.1.1 (Python 3.13). The present report
  is about *modified* shipped files in the framework, a different cause.
- **tdf#167752** "Python not working anymore on recent versions of LO on macOS"
(NEW): the parent launch constraint
  means `Contents/Resources/python` cannot be started from Terminal. That is
why step B uses an in-process macro. Not a
  duplicate.
- tdf#106324 (tests folder in LibreOfficePython.framework, FIXED 2018) and
tdf#62442 / tdf#93331 (language pack writes
  into the bundle). Earlier cases of bundle integrity being broken by content
changes; not duplicates.

## Attachments (planned)

- `check_pyc_headers.py`
- full per-file list (`check_pyc_headers.py <root> --list`)

-- 
You are receiving this mail because:
You are the assignee for the bug.

Reply via email to