diff --git a/.github/dm-consumer.yml b/.github/dm-consumer.yml index d4261d16..b61f2810 100644 --- a/.github/dm-consumer.yml +++ b/.github/dm-consumer.yml @@ -37,3 +37,8 @@ codeql-cpp: ## CodeQL's manual build-mode tracing. prepare-command: "ci/codeql-prepare.sh" build-command: "ci/codeql-build.sh" + +dist-ai-tests: + ## The ssh-audit config suite (security-misc-tests) stands up a + ## throwaway sshd and drives ssh-audit; python3-pytest is baseline. + apt-packages: "openssh-server openssh-client ssh-audit" diff --git a/.github/workflows/consumer-claude-code.yml b/.github/workflows/consumer-claude-code.yml deleted file mode 100644 index a81d2f7f..00000000 --- a/.github/workflows/consumer-claude-code.yml +++ /dev/null @@ -1,85 +0,0 @@ -## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC -## See the file COPYING for copying conditions. - -## AI-Assisted - -## Managed by pkg_update_consumer_workflows. Byte-identical -## across consumers; this file is `cp`-ed from -## developer-meta-files/consumer-templates/.github/workflows/consumer-claude-code.yml. -## DO NOT hand-edit this file in the consumer repo - changes -## will be overwritten on the next propagation pass. -## -## Reusable docs: -## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-claude-code-review.yml - -name: Claude Code Review - -## Mention-driven only. claude-code-action picks its mode from the -## event: comment events with no 'prompt' input select tag mode, -## where the '@claude' phrase is the trigger and the model runs. -## Every other event (pull_request, workflow_dispatch, schedule) -## selects agent mode, whose trigger is a non-empty 'prompt' - which -## this workflow does not pass - so those events can only spin up a -## runner and exit without calling the model. Listing them here -## would put a green "Claude Code Review" check on every PR that -## never reviewed anything. See the reusable's header for the full -## rationale and for what enabling real auto-review would cost. -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - -permissions: - contents: read - -## The two comment events differ: issue_comment fires with -## github.ref = the default branch, pull_request_review_comment with -## the PR merge ref (refs/pull//merge). Neither is the PR head -## ref, and plain ${{ github.ref }} would queue unrelated PRs' -## comment-triggered runs into the same group. The PR/issue-number -## chain below is what actually applies: one of those two number -## fields is populated for every event this workflow accepts, so -## github.ref is a fallback that is never reached here. -## -## Bot-vs-human split via the trailing actor-class suffix: when the -## Claude action posts a review comment, it does so as claude[bot], -## which fires this workflow again. Without the split, that -## bot-triggered run would land in the same concurrency group as the -## in-flight human @claude request, and 'cancel-in-progress: true' -## below would cancel the review mid-flight before any output -## reached the PR. The job-level 'if:' filter further down catches -## the bot run (claude[bot]'s own comment body does not contain -## '@claude'), but by then concurrency has already done the damage. -## GitHub's standard '[bot]' suffix on App accounts (claude[bot], -## github-advanced-security[bot], dependabot[bot], ...) lets a single -## endsWith() check isolate all bot-authored events into a separate -## group so they only cancel each other. -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}-${{ endsWith(github.actor, '[bot]') && 'bot' || 'human' }} - cancel-in-progress: true - -jobs: - review: - ## Pre-filter for the '@claude' substring before the runner spins - ## up. The reusable's job-level 'if:' enforces the same gate plus - ## the allowed-users roster check, but doing the cheap substring - ## test here too prevents a runner from starting (and stopping - ## seconds later) for every unrelated comment on every issue/PR. - if: contains(github.event.comment.body, '@claude') - uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-claude-code-review.yml@master - ## Explicit secret forwarding (NOT 'secrets: inherit'). With - ## inherit, every caller-side secret would flow into the - ## reusable; the explicit map caps the reusable's secrets - ## context to exactly CLAUDE_CODE_OAUTH_TOKEN. - secrets: - CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - ## 'contents: read' does NOT bound what Claude can do - the - ## action swaps the runner token for an OIDC-minted claude[bot] - ## App token carrying contents/issues/pull-requests write. The - ## block is required for 'id-token: write'. See the reusable. - permissions: - contents: read - pull-requests: write - issues: write - id-token: write diff --git a/.github/workflows/consumer-codex-review.yml b/.github/workflows/consumer-codex-review.yml deleted file mode 100644 index f333728c..00000000 --- a/.github/workflows/consumer-codex-review.yml +++ /dev/null @@ -1,46 +0,0 @@ -## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC -## See the file COPYING for copying conditions. - -## AI-Assisted - -## Managed by pkg_update_consumer_workflows. Byte-identical -## across consumers; this file is `cp`-ed from -## developer-meta-files/consumer-templates/.github/workflows/consumer-codex-review.yml. -## DO NOT hand-edit this file in the consumer repo - changes -## will be overwritten on the next propagation pass. -## -## Reusable docs: -## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-codex-review.yml - -name: OpenAI Codex Review - -on: - pull_request: - branches: [master] - workflow_dispatch: - inputs: - pr_ref: - description: | - Ref of the fork PR to review. - Use `refs/pull//merge` (most accurate; mirrors what - would land), or `refs/pull//head` for the PR head. - A plain branch name in the upstream repo also works for - non-fork PRs. - required: true - type: string - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - review: - uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-codex-review.yml@master - with: - pr-ref: ${{ inputs.pr_ref || '' }} - permissions: - contents: read - pull-requests: write diff --git a/.github/workflows/consumer-secrets-audit.yml b/.github/workflows/consumer-dist-ai-tests.yml similarity index 52% rename from .github/workflows/consumer-secrets-audit.yml rename to .github/workflows/consumer-dist-ai-tests.yml index 579f9915..f9894d7e 100644 --- a/.github/workflows/consumer-secrets-audit.yml +++ b/.github/workflows/consumer-dist-ai-tests.yml @@ -5,27 +5,25 @@ ## Managed by pkg_update_consumer_workflows. Byte-identical ## across consumers; this file is `cp`-ed from -## developer-meta-files/consumer-templates/.github/workflows/consumer-secrets-audit.yml. +## developer-meta-files/consumer-templates/.github/workflows/consumer-dist-ai-tests.yml. ## DO NOT hand-edit this file in the consumer repo - changes ## will be overwritten on the next propagation pass. ## -## Manual-trigger entry point. Run from the Actions tab: -## 'Secrets surface audit' > 'Run workflow'. Use as a sanity -## check after changing any reusable's 'workflow_call.secrets' -## schema or any consumer's 'secrets:' map. -## -## 'secrets: inherit' here forwards the entire repo-level + -## org-level secret set into the audit reusable, which prints -## presence flags. The audit boolean is resolved at -## expression-evaluation time, so secret values themselves never -## land in env or step output. +## Runs this package's comprehensive dist-ai regression suites (which +## live in org-ai-assisted/dist-ai, not in-tree) on each PR and on push +## to master. The component is derived from the repo name by the +## reusable, so this wrapper carries no per-repo state. ## ## Reusable docs: -## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-secrets-audit.yml +## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-dist-ai-tests.yml -name: Secrets surface audit +name: dist-ai tests on: + pull_request: + branches: [master] + push: + branches: [master] workflow_dispatch: permissions: @@ -36,6 +34,5 @@ concurrency: cancel-in-progress: true jobs: - audit: - uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-secrets-audit.yml@master - secrets: inherit + dist-ai-tests: + uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-dist-ai-tests.yml@master diff --git a/.github/workflows/local-dist-ai-ssh-prod.yml b/.github/workflows/local-dist-ai-ssh-prod.yml new file mode 100644 index 00000000..901fd493 --- /dev/null +++ b/.github/workflows/local-dist-ai-ssh-prod.yml @@ -0,0 +1,41 @@ +## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## AI-Assisted + +## Hard-gating scan of the live production SSH servers (kicksecure.com, +## whonix.org) against security-misc's shipped ssh-audit server policy, via the +## dist-ai --e2e suite security-misc-tests-e2e (which lives in +## org-ai-assisted/dist-ai, not in-tree). This is NOT the managed +## consumer-dist-ai-tests.yml (--core) template. +## +## It runs on push to master, on a daily schedule, and on demand -- NOT on +## every pull request: a feature PR cannot change the production servers, so +## gating unrelated PRs on live external state would only wedge them. A failure +## here means a production server dropped (or never had) the security-misc SSH +## hardening; fix is on the host. +## +## Reusable docs: +## https://github.com/org-ai-assisted/developer-meta-files/blob/master/.github/workflows/reusable-dist-ai-tests.yml + +name: dist-ai ssh prod + +on: + push: + branches: [master] + schedule: + - cron: '17 6 * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + dist-ai-ssh-prod: + uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-dist-ai-tests.yml@master + with: + categories: '--e2e' diff --git a/.github/workflows/local-permission-hardener-test.yml b/.github/workflows/local-permission-hardener-test.yml new file mode 100644 index 00000000..e1d7e320 --- /dev/null +++ b/.github/workflows/local-permission-hardener-test.yml @@ -0,0 +1,64 @@ +--- +## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## AI-Assisted + +## Regression test for the permission-hardener config parser. +## +## Scope is intentionally narrow: it exercises load_state()'s +## right-anchored option parsing so a config filename containing a +## space is parsed as one entry instead of being split and silently +## dropped. See ci/tests/permission_hardener/test_whitespace_filename.sh. + +## FIXME: The actual scripts run here should be in dist-ai. + +## WARNING: The new test scripts will not be reviewed by humans as they are +## slated to be moved to dist-ai. This workflow file has been reviewed. + +name: Test permission-hardener + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Test permission-hardener + runs-on: ubuntu-latest + timeout-minutes: 10 + + ## CI runs only where we enabled it; ANDed with the existing guard. + ## Unset variable -> skipped, no runner, run stays green. + if: >- + vars.CI_ENABLED_ORG_AI_ASSISTED == 'true' + && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + ## permission-hardener sources helper-scripts (log_run_die.sh, + ## safe_echo.sh, strings.bsh) at startup; install-deps provides + ## them under /usr/libexec/helper-scripts/. + - name: Install genmkfile + helper-scripts + safe-rm + uses: org-ai-assisted/developer-meta-files/.github/actions/install-deps@master + with: + apt-packages: 'safe-rm' + + - name: Whitespace-in-filename parser regression test + ## Root: the test writes a temp config under + ## /etc/permission-hardener.d/ and runs 'print-policy'. + run: sudo -E ci/tests/permission_hardener/test_whitespace_filename.sh diff --git a/changelog.upstream b/changelog.upstream index ce87d20c..7d8ff45c 100644 --- a/changelog.upstream +++ b/changelog.upstream @@ -1,3 +1,564 @@ +commit 7f60beb8bffa344453bd6794f92ff72b6098edf3 +Merge: d80e26de cbba86cb +Author: Patrick Schleizer +Date: Wed Sep 30 04:20:30 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit cbba86cb06d7acd46aaf780f9cd1e5ab4ac97d8d +Author: Aaron Rainbolt +Date: Tue Sep 29 22:57:46 2026 -0400 + + Polish, reintroduce removed ciphers again but keep the requirement to use post-quantum key exchange + +commit 5202e7564b9133ae267c6064c88e69d23d45cd87 +Merge: d80e26de c3b4e219 +Author: Aaron Rainbolt +Date: Tue Sep 29 22:33:57 2026 -0400 + + Merge branch 'ai' into arraybolt3/trixie + +commit c3b4e219acaf5b853c8a3d71f32360ae5db3aa32 +Merge: 96c0366c d217e28d +Author: assisted-by-ai +Date: Tue Sep 29 04:28:20 2026 -0400 + + Merge arraybolt3/trixie into ai (remaster) + +commit d80e26defda7271a513cb6ba8ddee7c3e9ae83cf +Author: Patrick Schleizer +Date: Tue Sep 29 08:08:57 2026 +0000 + + bumped changelog version + +commit 96c0366cdc53005b5a06104d6c9c325c12c87882 +Author: claude (dev706 06a4d27a-451a-4444-b924-ffce1da248b5) +Date: Mon Sep 28 02:58:45 2026 -0400 + + ssh: fix override advice; sync interop comments + + - OpenSSH is first-wins: a lower-numbered drop-in (or ~/.ssh/config) overrides, not a higher-numbered one + + - server comment no longer claims the client stays broad; the client config is equally strict + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit aba02ec3634c006ea993c4609cf9a31c2e65a2b4 +Merge: 9cb4efa8 d217e28d +Author: Patrick Schleizer +Date: Mon Sep 28 02:40:50 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit 802205a4b8e9519ebd04eda34f5428ec8719ed59 +Author: claude (dev706 06a4d27a-451a-4444-b924-ffce1da248b5) +Date: Mon Sep 28 01:19:07 2026 -0400 + + ssh client: post-quantum-only kex + aes256-gcm (match server) + + - The client default now uses the same strictest set as the server: PQ-only + key exchange and the aes256-gcm cipher only (drop curve25519, chacha20, + aes256-ctr). Outbound SSH therefore only reaches post-quantum-capable + servers; override per-host in ~/.ssh/config or a 50_user.conf drop-in for a + server with a narrower algorithm set. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit d217e28daaaa3ef05d3f489180ec920eb2a4e18a +Author: Aaron Rainbolt +Date: Sun Sep 27 21:11:28 2026 -0400 + + Undo dangerous SSH settings changes applied by AI + +commit 6cee7b5659c60bd6e2f995786c178cb575f8c980 +Merge: 9cb4efa8 0a35d857 +Author: Aaron Rainbolt +Date: Sun Sep 27 20:49:32 2026 -0400 + + Merge branch 'ai' into arraybolt3/trixie + +commit 0a35d857cf38f02e99389bbfc0e299619b4105da +Author: claude (dev706 06a4d27a-451a-4444-b924-ffce1da248b5) +Date: Sun Sep 27 08:22:31 2026 -0400 + + sshd: post-quantum-only kex + aes256-gcm (sshaudit.com A+/100) + + - Server default now offers PQ-only key exchange (drop classical curve25519) + and an AEAD, Terrapin-immune cipher (aes256-gcm, drop chacha20). This is the + A+ / 100-of-100 sshaudit.com profile (verified equivalent to grapheneos.org): + resists harvest-now-decrypt-later and removes chacha20's residual Terrapin + exposure for unpatched peers. + - Interop: requires an OpenSSH 9.0+ (post-quantum) peer. Client config stays + broad so outbound SSH to older servers is unaffected. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit fcbf940c25f57af3ddf2ec8dda1e2f8e95847bda +Author: claude (dev706 06a4d27a-451a-4444-b924-ffce1da248b5) +Date: Sun Sep 27 04:21:49 2026 -0400 + + Move ssh-audit test tool + policies out to dist-ai + + - The ssh-audit-test driver and the server/client ssh-audit policies are test + artifacts, not runtime hardening; they now live in the dist-ai + security-misc-tests suite. security-misc's shipped package stays lean. + - Kept here: only the CI wiring (dm-consumer.yml + the dist-ai-tests / + ssh-prod workflows), which is inherently per-consumer-repo. The dist-ai + drift-guard test keeps the moved policies in sync with the ssh config drop-ins + that remain the source of truth. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 6d70b404e1812ade14912840263bf4671890dcc6 +Author: claude (dev706 06a4d27a-451a-4444-b924-ffce1da248b5) +Date: Sat Sep 26 02:54:03 2026 -0400 + + ssh-audit-test: clean error on missing option operand + + - --policy / --port with no value crashed on shift (shift count out of range) + instead of reporting the missing operand; validate and die cleanly. + - Document that the ssh-audit policy check is server-to-client; OpenSSH sets + both directions symmetrically, so it is complete for our OpenSSH targets. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 3ce6b81d5f7f9f1561d33ae0f474d11de8021195 +Author: claude (dev706 06a4d27a-451a-4444-b924-ffce1da248b5) +Date: Sat Sep 26 01:46:37 2026 -0400 + + Add ssh-audit hardening policies, test tool and CI wiring + + - ssh-audit-test + server/client policies pin the exact hardened algorithm + set (allow_algorithm_subset_and_reordering = false); a regression that + loosens the ssh config, or a server missing the hardening, now fails loud. + - dist-ai consumer wiring runs the config suite per PR and scans production + SSH on a schedule. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 2bf04eee21a66fed4f3c45556ebd362cfc6f810f +Merge: 00bad984 9cb4efa8 +Author: assisted-by-ai +Date: Thu Sep 24 22:51:46 2026 -0400 + + Merge master into ai (remaster) + +commit 9cb4efa8a716343271e3339fb7e13408146b7943 +Author: Patrick Schleizer +Date: Sat Sep 19 14:09:18 2026 +0000 + + bumped changelog version + +commit f7e4aa5ed2dbf0cd0d9155695ad4fdf315a1181f +Merge: a70c168d 87ff42a1 +Author: Patrick Schleizer +Date: Sat Sep 19 10:00:10 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit 87ff42a19c786050471b5350e228a26a82bd61eb +Author: Aaron Rainbolt +Date: Wed Sep 16 00:01:44 2026 -0400 + + Comments, revert an unreliable feature addition in permission-hardener's state file parsing + +commit 3bc6ec2a046f20d292273095c40352ec6d914a52 +Merge: a70c168d 00bad984 +Author: Aaron Rainbolt +Date: Tue Sep 15 23:26:46 2026 -0400 + + Merge branch 'ai' into arraybolt3/trixie + +commit 00bad98497794d461fe4923e4ff182a61b918df7 +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Tue Sep 15 10:56:10 2026 -0400 + + security-misc: source has.bsh explicitly in preinst + +commit 57a6530574d21307d77a371a1fb53259bfbe22fa +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Tue Sep 15 10:52:46 2026 -0400 + + security-misc: simplify preinst helper-scripts sourcing + + - helper-scripts is a Pre-Depends, configured before this preinst; strict + options first, then plain source -- errexit turns any load failure into a + hard stop, so the bespoke fail-closed handling + type -t check are redundant + +commit f0ea22f6b0f631485fb8f405eb352d35a3d33d84 +Merge: 888a5f67 a70c168d +Author: assisted-by-ai +Date: Tue Sep 15 10:45:48 2026 -0400 + + Merge master into ai (remaster) + +commit a70c168da6730ce86d1ea12f29bff2448734a8a9 +Author: Patrick Schleizer +Date: Tue Sep 15 14:32:35 2026 +0000 + + bumped changelog version + +commit 0a004b6675d22e4e54f79f07b086cebe78177280 +Author: Patrick Schleizer +Date: Tue Sep 15 10:27:21 2026 -0400 + + output + +commit edbaf1c198cb727ab00ff57ef573edc8c7974fd6 +Author: Patrick Schleizer +Date: Tue Sep 15 12:00:55 2026 +0000 + + bumped changelog version + +commit 888a5f679d816aeb8c50fc14ab604d27d158863d +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Tue Sep 15 04:27:13 2026 -0400 + + permission-hardener: restore octal-owner filename disambiguation + + - the right-to-left octal scan mis-anchors on a numeric octal-looking owner + (e.g. 0755), folding the real mode into the filename; the nonexistent path is + then silently dropped, leaving a SUID binary un-hardened. + - restore fixed-width tail anchoring + disambiguation by on-disk existence; + test_whitespace_filename.sh (numowner, 'x y') parses again. + +commit c894bbcf75bbe3c63f17b75f0ffc8a0ae2f98ef3 +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Tue Sep 15 02:37:02 2026 -0400 + + preinst: fail closed loudly if helper-scripts library missing + + - source failures were swallowed (errexit not set yet); abort with the real + cause instead. + - a missing group_has_nonroot_member now aborts with the true reason rather + than degrading the lockout check into a misleading 'no account is a member of + sudo' abort. + +commit 239532762966b9fc57f158bb02fb19b04bf1e1bc +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Tue Sep 15 02:16:06 2026 -0400 + + Pre-Depend on helper-scripts; source accountctl.sh, drop vendored copy + + - helper-scripts moved from Depends to Pre-Depends: it is configured before this + package is unpacked, so the preinst can source its libraries. Reuse the real + group_has_nonroot_member from accountctl.sh instead of a vendored copy (and its + drift guard). + - accountctl.sh pulls in has.bsh, so 'has' is available: use it, drop the + no-has waiver and the command -v fallbacks. + +commit 497a23fbb31e2550fc858e6139e84b595be49d20 +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Mon Sep 14 12:49:30 2026 -0400 + + Sync vendored group_has_nonroot_member (reject numeric arg) + +commit 88abbcaa436a86dcd15a37c8ba4f4351b565c050 +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Mon Sep 14 11:13:01 2026 -0400 + + Detect primary-GID members in the install checks + + - sudo_users_check / console_users_check now count a non-root account + whose PRIMARY group is sudo/console (via a vendored + group_has_nonroot_member), not only supplementary members; a + supplementary-only scan falsely aborted install for such an admin. + - Revert the qubesdb-read probe to 'command -v' (no-has waiver): a + preinst runs at unpack time, before helper-scripts is guaranteed + configured, so it must not source has.bsh. + - group_has_nonroot_member is VENDORED from helper-scripts accountctl.sh + (same reason it cannot be sourced); a dist-ai drift guard keeps them + in sync. + +commit dc103b260b2dfd8b94ccdd23aaaaa54000d8d56c +Author: claude (dev556 4bdc306f-7204-4d7f-ba68-0c897dcf0291) +Date: Mon Sep 14 09:35:22 2026 -0400 + + Also skip install checks on dist_build_iso; house-style + + - Skip the sudo/console-user install checks when dist_build_iso=true (an + ISO/live build) in addition to the existing skip_install_check file, so + the derivative-maker build no longer needs to write that marker file. + The manual file-based bypass is kept for non-derivative-maker users. + - Modernize to dist-ai house style: strict-mode block, printf not echo, + has not command -v, braced + nounset-guarded variables. + +commit 4db7b41bda5f7f2a1d0c1f163a070c998a5a284e +Merge: 81d6905a 827e5ee1 +Author: Patrick Schleizer +Date: Mon Sep 14 02:29:46 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit 827e5ee1f8a250ba8b04e08c91892b1887e83d61 +Author: Aaron Rainbolt +Date: Sun Sep 13 17:50:16 2026 -0400 + + comments + +commit a85c5b57045e95a840c4059e66d8296b8e51c155 +Merge: d108f60a 28d05b0c +Author: Aaron Rainbolt +Date: Sun Sep 13 17:40:14 2026 -0400 + + Merge branch 'arraybolt3/maybebyte-pam-auth-update' into arraybolt3/trixie + +commit 28d05b0cd17c9f3d77786cf14798c1cdec912d0c +Merge: 86780917 d108f60a +Author: Aaron Rainbolt +Date: Sun Sep 13 17:39:32 2026 -0400 + + Merge branch 'arraybolt3/trixie' into arraybolt3/maybebyte-pam-auth-update + +commit d108f60a20cfc51cbe3d98f6200e8387a47f7488 +Author: Aaron Rainbolt +Date: Sun Sep 13 17:25:53 2026 -0400 + + comment + +commit ac4b5023ae7c0ca034b8e85dda91d34de4e2e920 +Author: Aaron Rainbolt +Date: Sun Sep 13 17:21:40 2026 -0400 + + Polish + +commit 81ed36c2e3e5777ca81e33f8a40edd1cd952e001 +Merge: 439ddce5 db8f36fd +Author: Aaron Rainbolt +Date: Sun Sep 13 17:11:40 2026 -0400 + + Merge branch 'ai' into arraybolt3/trixie + +commit db8f36fd5ab660ad1c2dd02fffa998a2b3f1ee58 +Merge: e64a2c7d b8e218c6 +Author: claude +Date: Sun Sep 13 06:07:26 2026 -0400 + + Merge branch 'master' into ai + +commit b8e218c6438b862acd881eac9599ff40224fe862 +Merge: c77beac7 b9cf5081 +Author: claude +Date: Sun Sep 13 06:07:25 2026 -0400 + + Merge remote-tracking branch 'upstream/master' + +commit e64a2c7d0481dfd38c8a575a49ddf55248e6230d +Author: claude (dev541 abd2cdfa-632f-4eda-bd69-14b67be2017e) +Date: Sun Sep 13 05:28:23 2026 -0400 + + Python -Bsu shebangs; grep --quiet in tests + +commit 81d6905af8bd14a21163a1ab308c996e38869f74 +Merge: b9cf5081 439ddce5 +Author: Patrick Schleizer +Date: Sat Sep 12 09:49:38 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit 439ddce5bf08465710b81bd2353ac105b1e522c4 +Author: Aaron Rainbolt +Date: Fri Sep 11 17:06:29 2026 -0400 + + Revert some faulty changes to style-ok comments (uglier style, excessive verbosity, allow-echo in a script that did not use echo) + +commit d4e50dc6d74b6cf13c3dff2ef2b7d16a2b682ec2 +Merge: b9cf5081 409e0483 +Author: Aaron Rainbolt +Date: Fri Sep 11 16:58:09 2026 -0400 + + Merge branch 'ai' into arraybolt3/trixie + +commit b9cf508116757b7fff13c199a142742246162877 +Author: Patrick Schleizer +Date: Wed Sep 9 11:05:44 2026 +0000 + + bumped changelog version + +commit 0ba912995c461446bf527cf0c6a9db47e8aaab08 +Merge: fc2afb15 dd05179e +Author: Patrick Schleizer +Date: Wed Sep 9 06:56:30 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit 409e0483071c2ca6a313272ec1957aab80a8cc3f +Merge: 24b39b0c c77beac7 +Author: claude (dev490 427cd1a1-bb80-4a36-9e14-2b6cec600780) +Date: Tue Sep 8 23:45:54 2026 -0400 + + Merge branch 'master' into ai + + # Conflicts: + # debian/security-misc-shared.postinst + # usr/bin/remount-secure#security-misc-shared + # usr/libexec/security-misc/check-for-usb-controller#security-misc-shared + # usr/share/lintian/overrides/security-misc-shared#security-misc-shared + # usr/share/security-misc/lkrg/lkrg-virtualbox#security-misc-shared + +commit c77beac7b4db560cd6d58abbc8943b77ee0ae62c +Merge: 6355b72c dd05179e +Author: claude (dev490 427cd1a1-bb80-4a36-9e14-2b6cec600780) +Date: Tue Sep 8 23:43:07 2026 -0400 + + Merge remote-tracking branch 'ab/trixie' + +commit 6355b72c6d9de2002506a3ab797b153f83c92a9d +Merge: 78491468 fc2afb15 +Author: claude (dev490 427cd1a1-bb80-4a36-9e14-2b6cec600780) +Date: Tue Sep 8 23:43:06 2026 -0400 + + Merge remote-tracking branch 'up/master' + +commit dd05179ea3310d0cac4d2de9db220ebbd5224fbf +Author: Aaron Rainbolt +Date: Tue Sep 8 21:24:00 2026 -0400 + + fix + +commit fc2afb158592f8a8ad7270dcb5ee5abb33e1824d +Author: Patrick Schleizer +Date: Tue Sep 8 15:30:04 2026 +0000 + + bumped changelog version + +commit 24b39b0ca1b6c5a0fd35036703f56a2a1f5b9225 +Author: claude (dev474 c7439536-fad8-4884-b71a-d2f3c4c23905) +Date: Mon Sep 7 11:49:54 2026 -0400 + + rename has.sh to has.bsh (bashism convention) + + Sourced helper-scripts lib uses bashisms; .bsh marks it. Scoped to has.sh consumers (repo carries unrelated concurrent WIP). + +commit 24a55b78fe94ef69bb866f71b86d54e3a659b83c +Merge: 94ea3994 aa458bac +Author: Patrick Schleizer +Date: Mon Sep 7 01:11:01 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit aa458bac395d6fd450c749d0934f4a63d4a76c17 +Author: Aaron Rainbolt +Date: Sun Sep 6 23:11:56 2026 -0400 + + Fix coding style, shorten comments + +commit 97f78d8262aeea854cef3d654ab6aeb57e1f08ec +Merge: 94ea3994 077d41a6 +Author: Aaron Rainbolt +Date: Sun Sep 6 22:38:54 2026 -0400 + + Merge remote-tracking branch 'maybebyte/fix/remove-system-map-readonly-continue' into arraybolt3/trixie + +commit 94ea3994027115b02bdcc8d8246c4c5b5c40a776 +Author: Patrick Schleizer +Date: Sun Sep 6 16:58:23 2026 +0000 + + bumped changelog version + +commit 0088727e4a9ca17d07916a30544ffa6e13890bfc +Author: Patrick Schleizer +Date: Sun Sep 6 12:56:41 2026 -0400 + + cleanup + +commit ebd72d593c81fad95d09a055d4e839b6eecca332 +Merge: 78491468 87506b1e +Author: Patrick Schleizer +Date: Sun Aug 30 10:02:32 2026 -0400 + + Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' + +commit 3e3d7453e3318ac79ea36ae14ed90442ded0c165 +Author: claude (dev371 3fff1819-b77d-4dde-be19-8c01e09d3394) +Date: Fri Aug 28 01:23:45 2026 -0400 + + lintian: drop redundant maintainer-script-ignores-errors override + + Now suppressed globally in genmkfile; the false positive is the same errexit long-form case. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 87506b1eaf45fa7501d54cf83b9be13578f86ecb +Author: Aaron Rainbolt +Date: Fri Aug 28 01:03:22 2026 -0400 + + Polish, add TODOs + +commit 0f1f701b5c413b1ab2229577261fe6e0187d84ca +Merge: eb5b301d bb399ba7 +Author: Aaron Rainbolt +Date: Thu Aug 27 21:02:55 2026 -0400 + + Merge branch 'ai' into arraybolt3/trixie + +commit bb399ba73704e4c20fbc6965d13bee4f2c63626c +Author: claude (dev352 385b930f-4313-4883-93d5-e6857c3f7ea7) +Date: Thu Aug 27 12:07:58 2026 -0400 + + lintian: override maintainer-script-ignores-errors in the shipped file + + The postinst uses long-form 'set -o errexit' (style R-013); lintian's + scripts check only matches short 'set -e'. Add the false-positive + override to the package's own usr/share/lintian/overrides file -- a + separate debian/*.lintian-overrides displaces it (dh_lintian installs to + the same path), losing the etc-skel and other overrides it carries. + +commit d1b9f4c0517f03936af556445fedd2ee22d14e98 +Merge: cc7a93d1 908cadc6 +Author: claude (dev352 385b930f-4313-4883-93d5-e6857c3f7ea7) +Date: Thu Aug 27 11:22:32 2026 -0400 + + Merge branch 'ai' of ssh://github.com/org-ai-assisted/security-misc into ai + +commit cc7a93d10a30e19631c64437b5c6078ba5d74f77 +Author: claude (dev352 385b930f-4313-4883-93d5-e6857c3f7ea7) +Date: Thu Aug 27 11:21:51 2026 -0400 + + lintian: override false-positive maintainer-script-ignores-errors + + The postinst enables errexit via long-form 'set -o errexit' (project + style, R-013); lintian's check only matches short 'set -e'. Same + override tb-updater already carries. + +commit 908cadc6177c00d4ff3465b33b20b3c971ce246a +Author: assisted-by-ai (Bot Account) (Machine Account) <228987186+assisted-by-ai@users.noreply.github.com> +Date: Mon Aug 24 23:31:21 2026 -0400 + + ci: drop orphaned consumer-secrets-audit.yml (reusable decommissioned) + +commit a6d7a3585f8905bc3f898d8dd83c3b07cb04cba9 +Author: assisted-by-ai (Bot Account) (Machine Account) <228987186+assisted-by-ai@users.noreply.github.com> +Date: Mon Aug 24 23:31:19 2026 -0400 + + ci: drop orphaned consumer-codex-review.yml (reusable decommissioned) + +commit e6a81266555130673dc7d8bcb85ea50bbad9e852 +Author: assisted-by-ai (Bot Account) (Machine Account) <228987186+assisted-by-ai@users.noreply.github.com> +Date: Mon Aug 24 23:31:16 2026 -0400 + + ci: drop orphaned consumer-claude-code.yml (reusable decommissioned) + +commit 1c9f144b9ebe58daa174de05f5b0c0e42038bfc1 +Author: claude (dev272 e8b76afb-795c-4739-8ebd-023de854021e) +Date: Fri Aug 21 04:41:27 2026 -0400 + + security-misc: source helper-scripts via HELPER_SCRIPTS_PATH, drop SC1091 disable + + - check-for-usb-controller, permission-hardener, lkrg-virtualbox: use + source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/; + drop the dead '# shellcheck disable=SC1091' and the stale sibling note. + - completed pre-existing whole-file style debt: R-010 export LC_ALL=C in + the strict-mode block (dropping the duplicate single-quoted mid-file one). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 78491468a7c209f423f48f74de015f1df2b9a980 +Author: Patrick Schleizer +Date: Sun Aug 16 08:24:20 2026 +0000 + + bumped changelog version + commit f1e9da8fb911b1e3962f219290df14753794aee8 Merge: 52e18f72 1fcf4967 Author: Patrick Schleizer @@ -5,6 +566,302 @@ Date: Sat Aug 15 12:55:45 2026 -0400 Merge remote-tracking branch 'refs/remotes/org-ai-assisted/master' +commit f2c1e7517cf8a8017db91ef7cf42784e93e14ae8 +Author: claude (reviewdrain4 8d32e35d-2a57-4379-829c-6ef986f68cd4) +Date: Fri Aug 14 13:28:37 2026 -0400 + + permission-hardener: read spaced paths back from the state DB + + Fresh-claude review of the whitespace feature found the write/read sides + disagree: parsing now accepts space-containing filenames into the policy, but + load_state_without_policy split the dpkg-statoverride DB line on whitespace and + required exactly 4 fields, silently dropping any spaced path. A spaced SUID file + then got hardened but could never be un-hardened ('disable' never found it), and + repeated 'enable' failed with exit 203. + + - Read the three fixed fields (owner group mode) and take the remainder as the + path, matching the DB's 'owner group mode path' format. + - Regression test test_state_spaced_filename.sh: seed a spaced entry, disable, + assert the mode is restored (fails on the old reader: file stays un-restored). + - Test hygiene: print-policy captures modes into the state DB, so the two + print-policy tests now remove the entries they create instead of leaving stale + root-owned overrides for deleted temp files. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 6dcffee4bf717bfac9a4c13153dcaa1a25cdc89f +Author: claude (reviewdrain4 8d32e35d-2a57-4379-829c-6ef986f68cd4) +Date: Fri Aug 14 13:12:25 2026 -0400 + + permission-hardener: unify mode anchoring across all field widths + + Fresh-claude follow-up: the field_count==4/5 existence override did not cover a + 6-field line (spaced filename + numeric octal-looking owner + capability), which + anchored the mode on the owner and silently dropped the entry. No hardening + bypass -- numeric owners are rejected later against passwd names -- but a silent + drop instead of a loud rejection, and the ==4 disjunct was dead code. + + Replace the special-case override with one existence-based loop: among the octal + candidates at field_count-3 and field_count-4, pick the one whose recovered + filename exists; a mode at field 1 means no-space (no recovery); fall back to + the rightmost octal candidate when neither name exists. Handles the 3-field, + 4-field, spaced, numeric-owner and 6-field cases uniformly. + + - Regression test: 6-field spaced numeric-owner case; the suite now + distinguishes this fix from all three prior parser iterations. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 73cc7889b271e2f3733daed276ba235dde04dc20 +Author: claude (reviewdrain4 8d32e35d-2a57-4379-829c-6ef986f68cd4) +Date: Fri Aug 14 13:00:51 2026 -0400 + + permission-hardener: disambiguate octal-ambiguous entries by file existence + + Fresh-claude review found the earlier field-1 guard reintroduced a symmetric + misparse: a spaced filename whose second component is a bare octal (e.g. + '/srv/a 744 0644 root root') got dropped because the guard mistook the octal + component for the mode. + + The 5-field reading is genuinely ambiguous lexically -- '/a 744 0644 root root' + (spaced name) vs '/foo 0744 1000 root cap' (numeric owner). Resolve by which + candidate filename exists on disk, consistent with the tool skipping + nonexistent targets. Keep the no-recovery reading only when field 1 is an octal + mode and only the un-recovered name exists. + + - Replace the lexical guard with the existence check inside the right-anchor + branch; the whitelist-keyword branch is no longer gated (it was wrongly + skipped for '/x 744 y matchwhitelist'). + - Regression test: spaced octal-second-component case; the assertion now matches + the whole first column (trailing TAB) so '/a 744' cannot substring-match the + '/a 744 name' entry. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 780561e61a6bf1c8d110d1105a5102b59ceb14ea +Author: claude (reviewdrain4 8d32e35d-2a57-4379-829c-6ef986f68cd4) +Date: Fri Aug 14 12:49:10 2026 -0400 + + permission-hardener: fix disable-all directive + numeric-owner misparse + + Both surfaced by a fresh-claude review of the right-anchor change. + + - whitelists_disable_all=true was unreachable: the character-class check (which + excludes '=') ran before the directive check, so the documented line tripped + "invalid characters" and aborted the whole run (exit 200). Recognize the + directive first. + - Numeric octal-looking owner in a capability entry (e.g. ' 0744 0755 root + cap'): right-anchoring folded the owner into the mode position, the folded + path did not exist, and the entry was silently dropped -- file left + un-hardened. Skip recovery when the line is already a well-formed no-space + entry (octal mode at field 1, count 4 or 5). + - Correct the stale field-order comment: the tail is + 'filemode user_owner group_owner [capability]', mode first (per add_to_policy). + - Regression tests: numeric-owner case in the whitespace test; new + test_whitelists_disable_all.sh. Both fail on the pre-fix code. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit fca4984733e4aff6b90e8dd248d38b8cbb3889cc +Author: claude (reviewdrain4 8d32e35d-2a57-4379-829c-6ef986f68cd4) +Date: Fri Aug 14 12:40:52 2026 -0400 + + permission-hardener confs: correct gst-ptp-helper path/pkg and #8633 fix credit + + - 30_default.conf: gst-ptp-helper ships in libgstreamer1.0-0, not + gstreamer1.0-plugins-base; fix the disabled entry's path to + .../gstreamer1.0/gstreamer-1.0/... (first component was gstreamer-1.0, + which does not exist). Verified via dpkg -S. + - 25_default_whitelist_qubes.conf: the #8633 EoP fix is qfile-unpacker + (qubes-core-agent-linux) refusing a UID change other than the caller's; + libqubes-pure path/symlink sanitization (qubes-linux-utils PR #87) is a + separate layer. The comment credited only PR #87. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 37ad483b25c43b93bb350983c2534f9d775e977a +Author: claude (reviewdrain4 8d32e35d-2a57-4379-829c-6ef986f68cd4) +Date: Fri Aug 14 12:34:17 2026 -0400 + + permission-hardener: anchor mode tail from the right for spaced filenames + + - Space filenames are recovered from the option tail; the mode/owner/group + [/capability] tail has a fixed width (3 or 4 fields). + - Old left-to-right octal scan misanchored when a space-delimited filename + chunk was itself a bare octal (e.g. '/path/a 744 name 0744 root root'): the + field-count check then failed and the WHOLE hardener aborted (exit 200), + contradicting the comment that claimed such a line was skipped. + - Anchor the mode from the right (field_count-3, else field_count-4); prefer + the 3-field reading so a filename ending in an octal chunk is not mistaken + for a 4-field capability tail. + - Regression test: config line with a bare-octal filename chunk must parse via + print-policy; fails on the old code (exit 200 / entry missing). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit c2fcb2d893bf26a59c1b25415333fafcad8682dc +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 05:15:00 2026 -0400 + + security-misc: drop versioned bash dependency + + - Revert the bash (>= 4.4) Depends. Supported Debian (trixie+) always + ships bash >= 4.4 and earlier is unsupported, so the inherit_errexit + builtin is always present; the dependency adds nothing. Policy bans it + (bash-style-guide R-010b). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit a66fa50019f95326fef372ec5b3d1cde3b59b4a9 +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:55:18 2026 -0400 + + security-misc: fix undo chown empty-arg + emerg fallback (ai-review) + + - undo_policy_for_file: the strict-mode brace pass quoted "${verbose}", + so 'permission-hardener disable all' ran 'chown "" -- ...' (invalid + empty owner) and 'chmod "" ...'. Make verbose an array so an empty + value expands to zero arguments. Canary-verified. + - emerg-shutdown fallback: when the prebuilt binary is absent, compile + straight into /run (tmpfs, writable even if /usr is read-only) rather + than via build-emerg-shutdown into /usr/bin. + - Document the space-filename parser limitations (consecutive spaces + collapse; an octal-looking path chunk can misanchor -> entry skipped). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit c4bf5403d0dbda87b2e1fe165ade7c81bf4e1b30 +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:44:07 2026 -0400 + + security-misc: declare bash (>= 4.4) for inherit_errexit + + - Many scripts use 'shopt -s inherit_errexit' (bash 4.4+) in their + strict-mode preamble; ${misc:Depends} does not supply the baseline, so + an older /bin/bash would die in the preamble before the script's logic. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 5b34c9406198a47ce0c8d78a143606056e9d51b0 +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:40:49 2026 -0400 + + security-misc: dfuzzer commit pin, marker rewords, remount-secure cleanup + + - ci/dfuzzer-build.sh: pin dfuzzer to the exact upstream commit and + verify the tag resolves to it (a moved tag now fails loudly). + - grub 40_kernel_hardening + remount-secure hidepid: reword standing + 'TODO' notes to 'REMINDER' (tracking, not pending work). + - remount-secure: replace the echo/true toggle with a printf-array + output helper; bring the file to strict-mode / shellcheck compliance + (6-directive block, braces) as required to touch it. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 9f12941585d5b18ae79f5d8dfd9f94cc0cd2507d +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:40:35 2026 -0400 + + security-misc: document whitelisted SUID/SGID helpers, flag human-only + + - Replace the bare 'TODO: research' markers on the permission-hardener + whitelist entries (postfix, selinux/utempter, spice, virtualbox, + qubes/qfile-unpacker, pam-tmpdir-helper, gst-ptp-helper) with concise + documentation: what each helper is, why it is SUID/SGID, what breaks if + stripped, and relevant CVE/hardening notes. + - Retag the keep-vs-strip decisions TODO-HUMAN-DEVELOPER-ONLY -- a + security-trust call for a human maintainer, not AI-resolvable. + - 30_default.conf: fix the commented gst-ptp-helper path typo + (gstreamer1.0/grstreamer-1.0 -> gstreamer-1.0/gstreamer-1.0); note that + filenames with spaces are now parsed. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 8aef705e63595beaa90f70e9c8a4f297fd7a08ee +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:40:20 2026 -0400 + + security-misc: fm-shim frontend calls xdg-mime/gio by absolute path + + - fm-shim-frontend invoked xdg-mime and gio by bare name, depending on + the systemd --user manager's PATH (minimal, profile-derived entries + absent) that fm-shim-backend hands it. Use /usr/bin/xdg-mime and + /usr/bin/gio, matching the existing /usr/bin/unicode-show call. + - Reword the backend TODO: the whole-session PATH fix belongs in + desktop-config-dist's start-lxqt-session (out of this repo); the + frontend absolute-path change is the in-repo mitigation. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 8ba6ca134e52411ce18f29b553dbfedb42b8ed3e +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:40:08 2026 -0400 + + security-misc: silence expected pam_exec journal noise via quiet_log + + - The [success=1 default=ignore] pam_exec lines run helper scripts that + exit 1 on their expected control-flow path, emitting an alarming + 'pam_exec(...): ... failed: exit code 1' journal line. Plain 'quiet' + suppresses only the user-facing message, not the log. + - Add 'quiet_log' (linux-pam >= 1.5.2, present on bookworm+) to every + skip-logic pam_exec line (console-lockdown, unix-faillock, + faillock-preauth, wheel), leaving the control flow byte-identical. + - Update the scripts' comments to point at the resolution. + + Note: not verified on a live login journal (needs a real PAM stack); + the change is additive and semantics-preserving regardless. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 8c991dd34247e7cc43843a1dff6d5300330646f1 +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:39:52 2026 -0400 + + security-misc: compile emerg-shutdown once at postinst, not every boot + + - emerg-shutdown.c was recompiled into /run on every boot. Move the + compile to a standalone build-emerg-shutdown run from postinst, which + installs /usr/bin/emerg-shutdown. Compiling on-target keeps the package + Architecture: all while still producing a correct per-arch static binary. + - Boot-time emerg-shutdown now just copies /usr/bin/emerg-shutdown into + /run (memlockd-resident), with a fallback build if the binary is absent. + - dracut module ships /usr/bin/emerg-shutdown (exists right after install, + fixing the require_binaries /run chicken-and-egg). + - Sandbox-verified: build-emerg-shutdown produces a static ELF in /usr/bin. + - Strict/style compliance on the touched scripts (waivers for the debconf + maintainer script and the dracut-sourced module). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 942ba8a1902c2c8a0d6cd1921f2b540eb413a217 +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 04:39:33 2026 -0400 + + security-misc: permission-hardener parse filenames with spaces + + - load_state() split config lines on every space, so any entry whose + filename contains a space was mis-fielded: silently skipped (a SUID + binary left un-hardened) or mapped to a corrupted policy entry. + - Recover the filename by parsing options from the right -- a trailing + whitelist keyword, or the mode/owner/group[/capability] tail anchored + by the octal mode. Space-free lines unchanged. + - Add regression test (drives the real script via print-policy) plus its + CI workflow. Canary-verified: fails on the pre-fix parser, passes now. + - Bring the file to strict-mode / shellcheck compliance as required to + touch it (6-directive block, printf, braces). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit ec78b99bb1be3be87fb07b3c269d54dc4f1db113 +Merge: a8f26061 1fcf4967 +Author: claude (dev188 4c9dda7c-c359-4064-afa5-2ec7ed54398c) +Date: Wed Aug 12 03:30:39 2026 -0400 + + Merge branch 'master' into ai + + Absorb upstream + fork-trunk work (merged PRs #23/#26, CI-workflow + propagation, changelog bump) so the PR diff carries only our own commits. + commit 52e18f723e37b5e5616ae6681f9e3cd28369e88c Merge: 2da1f21c eb5b301d Author: Patrick Schleizer @@ -12,6 +869,23 @@ Date: Wed Aug 12 03:02:01 2026 -0400 Merge remote-tracking branch 'refs/remotes/ArrayBolt3/arraybolt3/trixie' +commit a8f26061e0e199b95077aa20696257bddc3db73d +Author: claude (dev182 e09ec5cf-4ebf-4e1b-88e5-55fbac6ef1d3) +Date: Mon Aug 10 13:56:04 2026 -0400 + + Drain input in USB-controller and config-file lookups + + check-for-usb-controller: grep --quiet exits on the first match and closes + the pipe, sending SIGPIPE to lspci. Under pipefail the pipeline then reports + 141, so the guarding if takes the false branch even when a USB controller is + present. Let grep read the whole stream and redirect to /dev/null instead. + + mmap-rnd-bits: head -n 1 has the same early-close hazard against ls; use + sed -n '1p', which consumes all input, for consistency. Drop the redundant + duplicate set -euo pipefail line already covered by the long-form preamble. + + Co-Authored-By: Claude Opus 4.8 (1M context) + commit eb5b301db6069a5ccabf327745a000053ece1475 Merge: a5655e63 2da1f21c Author: Aaron Rainbolt @@ -259,6 +1133,88 @@ Date: Thu Jul 30 11:59:15 2026 -0400 Generated with assistance from Claude Code. +commit 867809170a265b269a34f6d704c97ec53744cd4d +Author: Ashlen +Date: Wed Jul 15 19:06:42 2026 -0600 + + fix(pam): pass profile names to pam-auth-update --remove + + 'pam-auth-update --remove' matches pam-config filenames under + /usr/share/pam-configs/, but prerm and postrm passed + $DPKG_MAINTSCRIPT_PACKAGE -- the package name security-misc-shared -- + which matches no profile. An unknown target is stored without + validation and simply never matches, so the --remove was inert in + both calls: the prerm call was an effective no-op, while the postrm + call still performed a plain reconciliation. + + The prerm early-disable was therefore dead. On removal + /etc/pam.d/common-* kept referencing helper scripts under + /usr/libexec/security-misc/ that dpkg then deleted; + block-unsafe-logins is a Priority 1100 'requisite' entry, so a + dangling reference fails all authentication until postrm's + reconciliation regenerates common-* -- permanently, if postrm fails + before that point. + + prerm now passes the eight profile filenames explicitly. postrm + drops the ineffective '--remove ' for a plain + reconciliation and guards it with 'command -v': Debian Policy 7.2 + gives no guarantee that dependencies are available when postrm runs + and requires postrm to gracefully skip such actions, and an absent + pam-auth-update would abort the script under 'set -e'. prerm stays + unguarded: Policy names Depends (here libpam-runtime) as the + mechanism prerm scripts rely on, dependencies there normally being + at least unpacked. + + One consequence of a '--remove' that now takes effect: it also drops + the eight names from /var/lib/pam/seen. If a removal is aborted, + postinst's 'pam-auth-update --package' re-enables the seven + 'Default: yes' profiles among them, including any of them an + administrator had deliberately disabled (other packages' profiles + remain in seen and are unaffected), while + console-lockdown-security-misc ('Default: no') stays off until an + administrator re-enables it -- reinstalling does not restore it, + since preinst's enable is behind a do_once stamp. Both are new + relative to the previous inert behavior. + +commit 077d41a6b193787dc31718370c1532d6638569bc +Author: Ashlen +Date: Wed Jul 15 12:54:05 2026 -0600 + + fix(remove-system.map): continue past undeletable files + + The deletion loop called `exit 0` on the first System.map that `[ -w ]` + rejected, so copies in later locations were never shredded. Where + `[ -w ]` passed but shred failed anyway, shred ran as a bare command and + tripped the ERR trap that pre.bsh installs, aborting with a "## Please + report this bug!" banner and exit 1. + + Branch on shred's exit status instead of `[ -w ]`, which does not + predict removability. Unlinking depends on write permission on the + parent directory rather than on the file itself, and an append-only file + passes the test yet cannot be unlinked at all. For a full-capability + root process the check is also true on a mode 444 file, so it only ever + failed on read-only file systems and on files carrying the immutable + attribute. + + Clear the immutable and append-only attributes before each attempt. Per + review, a System.map should never be immutable and `chattr +i` would + possibly complicate kernel package removal; append-only is cleared on + the same grounds, since it blocks removal without affecting `[ -w ]`. + + Report the files that remain once at the end, under a single wiki link + rather than repeating it on every line, and withhold "Done. Success." + when anything failed. A file reachable under two names, such as + /lib/modules//build and the source symlink beside it, is listed + under each, since both matched and both failed. + + Iterate an array rather than word-splitting a string with IFS, which a + /etc/remove-system.map_pre.d drop-in can set; under `IFS=:` the globs + matched nothing, so the script deleted nothing and still exited 0. + + The removal loop no longer exits early on failure and the script ends in + an explicit `exit 0`; a non-zero status would fail the kernel package's + postinst hook and the oneshot unit on every boot. + commit 5daefde4e8cb4705d8fbe7886f2e003e61e0b974 Author: claude (dev118 bd22decd-3027-4ab5-9e48-11adfe39b279) Date: Tue Jul 28 13:50:24 2026 -0400 diff --git a/ci/dfuzzer-build.sh b/ci/dfuzzer-build.sh index b536a002..607fe9ad 100755 --- a/ci/dfuzzer-build.sh +++ b/ci/dfuzzer-build.sh @@ -25,13 +25,18 @@ ## ## dfuzzer is NOT packaged in Ubuntu 24.04 noble (verified via ## packages.ubuntu.com - 'No such package'); hence the from-source -## build. Pinned to upstream tag v2.6 (latest release as of -## 2026-05-08). Bump when a new release lands. +## build. Pinned to upstream tag v2.6 AND its exact commit (verified +## below); latest release as of 2026-05-08. Bump both DFUZZER_TAG and +## DFUZZER_COMMIT together when a new release lands. set -o errexit set -o nounset set -o pipefail set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose + +## style-ok: no-tmp-hardcode (ephemeral CI build clone under /tmp/dfuzzer) if [ "${CI:-}" != "true" ] && [ "${ALLOW_LOCAL:-}" != "true" ]; then printf '%s\n' "${BASH_SOURCE[0]}: refusing to run outside CI. Set ALLOW_LOCAL=true to override." >&2 @@ -39,10 +44,17 @@ if [ "${CI:-}" != "true" ] && [ "${ALLOW_LOCAL:-}" != "true" ]; then fi DFUZZER_TAG="${DFUZZER_TAG:-v2.6}" +DFUZZER_COMMIT="${DFUZZER_COMMIT:-a955a80f7dd20fd7aeffad91da1c495aab5dbbd3}" -## TODO: Better to clone the whole repository, then check out a commit hash? git clone --depth 1 --branch "${DFUZZER_TAG}" \ https://github.com/dbus-fuzzer/dfuzzer /tmp/dfuzzer + +dfuzzer_actual_commit="$(git -C /tmp/dfuzzer rev-parse --verify HEAD)" +if [ "${dfuzzer_actual_commit}" != "${DFUZZER_COMMIT}" ]; then + printf '%s\n' "${BASH_SOURCE[0]}: dfuzzer tag ${DFUZZER_TAG} resolved to ${dfuzzer_actual_commit}, expected ${DFUZZER_COMMIT} -- refusing (tag moved?)." >&2 + exit 1 +fi + meson setup --buildtype=release /tmp/dfuzzer/build /tmp/dfuzzer ninja -C /tmp/dfuzzer/build -v sudo install -m 0755 /tmp/dfuzzer/build/dfuzzer /usr/local/bin/dfuzzer diff --git a/ci/tests/fm_shim_frontend/test_property.py b/ci/tests/fm_shim_frontend/test_property.py index e0f48a75..1361258e 100644 --- a/ci/tests/fm_shim_frontend/test_property.py +++ b/ci/tests/fm_shim_frontend/test_property.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 -su +#!/usr/bin/python3 -Bsu ## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. diff --git a/ci/tests/permission_hardener/test_state_spaced_filename.sh b/ci/tests/permission_hardener/test_state_spaced_filename.sh new file mode 100755 index 00000000..bfd77155 --- /dev/null +++ b/ci/tests/permission_hardener/test_state_spaced_filename.sh @@ -0,0 +1,70 @@ +#!/bin/bash +## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## AI-Assisted + +## WARNING: Not human-reviewed, MUST be moved to dist-ai. + +## Regression test: a hardened file whose path contains spaces must be +## un-hardenable via 'disable'. +## +## load_state_without_policy() reads the dpkg-statoverride state DB, one entry +## per line as 'owner group mode path'. A plain 4-field split drops any spaced +## path, so 'disable' never finds the entry and the file can never be restored. +## +## Seeds one spaced entry (mode 0744) into the state DB, sets the file to 0600, +## runs 'disable', and asserts the mode is restored to 0744 -- which only +## happens if load_state_without_policy read the spaced path back. +## +## Requires root: writes the state DB and chmod/chowns the target file. + +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)" +ph_bin="${PERMISSION_HARDENER_BIN:-${repo_root}/usr/bin/permission-hardener#security-misc-shared}" + +if [ ! -f "${ph_bin}" ]; then + printf '%s\n' "FAIL: permission-hardener not found at '${ph_bin}'." >&2 + exit 1 +fi + +## Must match store_dir in the script under test. +existing_mode_admindir='/var/lib/permission-hardener-v2/existing_mode' +mkdir -p -- "${existing_mode_admindir}" + +test_dir="$(mktemp -d -t ph-state-test.XXXXXX)" +spaced_file="${test_dir}/spaced tool" +touch -- "${spaced_file}" +chmod 0600 -- "${spaced_file}" + +## invoked indirectly via 'trap ... EXIT' +# shellcheck disable=SC2317 +cleanup() { + ## undo touches the master and new_mode DBs, not existing_mode, so remove the + ## seeded entry explicitly. + dpkg-statoverride --admindir "${existing_mode_admindir}" --remove \ + "${spaced_file}" >/dev/null 2>&1 || true + safe-rm -rf -- "${test_dir}" +} +trap cleanup EXIT + +## Seed the state DB: this is the record 'enable' would have written. +dpkg-statoverride --admindir "${existing_mode_admindir}" --add \ + root root 0744 "${spaced_file}" + +"${ph_bin}" disable "${spaced_file}" >/dev/null 2>&1 || true + +actual_mode="$(stat -c '%a' -- "${spaced_file}")" +if [ "${actual_mode}" = '744' ]; then + printf '%s\n' "PASS: spaced-path state entry read back; mode restored to 744." + exit 0 +fi + +printf '%s\n' "FAIL: mode is '${actual_mode}', expected '744' -- spaced state entry not read back, file cannot be un-hardened." >&2 +exit 1 diff --git a/ci/tests/permission_hardener/test_whitelists_disable_all.sh b/ci/tests/permission_hardener/test_whitelists_disable_all.sh new file mode 100755 index 00000000..e69b200c --- /dev/null +++ b/ci/tests/permission_hardener/test_whitelists_disable_all.sh @@ -0,0 +1,91 @@ +#!/bin/bash +## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## AI-Assisted + +## WARNING: Not human-reviewed, MUST be moved to dist-ai. + +## Regression test: the documented special directive 'whitelists_disable_all=true' +## must be honored, not rejected. +## +## The line contains '=', which is outside the config character-class allow-set. +## The directive check must run BEFORE that character-class validation; otherwise +## the documented line trips "invalid characters" and aborts the whole hardener +## (exit 200), so every other hardening rule in every conf is dropped too. +## +## Drives the REAL script via 'print-policy' with a conf that holds the directive +## plus one ordinary entry, and asserts print-policy exits 0 with the ordinary +## entry present. +## +## Requires root: writes a temporary config under /etc/permission-hardener.d/ +## and needs helper-scripts installed (sourced by permission-hardener). + +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)" +ph_bin="${PERMISSION_HARDENER_BIN:-${repo_root}/usr/bin/permission-hardener#security-misc-shared}" + +if [ ! -f "${ph_bin}" ]; then + printf '%s\n' "FAIL: permission-hardener not found at '${ph_bin}'." >&2 + exit 1 +fi + +test_dir="$(mktemp -d -t ph-wda-test.XXXXXX)" +ordinary_file="${test_dir}/ordinary" +touch -- "${ordinary_file}" + +config_dir="/etc/permission-hardener.d" +config_file="${config_dir}/zz-ai-whitelists-disable-all-test.conf" +mkdir -p -- "${config_dir}" + +## print-policy captures the file's current mode into this DB (must match +## store_dir in the script under test); remove the entry so a test run does not +## leave a stale root-owned override pointing at a deleted temp file. +existing_mode_admindir='/var/lib/permission-hardener-v2/existing_mode' + +## invoked indirectly via 'trap ... EXIT' +# shellcheck disable=SC2317 +cleanup() { + dpkg-statoverride --admindir "${existing_mode_admindir}" --remove \ + "${ordinary_file}" >/dev/null 2>&1 || true + safe-rm -f -- "${config_file}" + safe-rm -rf -- "${test_dir}" +} +trap cleanup EXIT + +{ + printf '%s\n' 'whitelists_disable_all=true' + printf '%s\n' "${ordinary_file} 0744 root root" +} > "${config_file}" + +## The directive line aborts the whole run (exit 200) on the buggy ordering; +## capture the status via an 'if' condition (errexit stays enabled). +if policy_output="$( "${ph_bin}" print-policy 2>/dev/null )"; then + ph_rc=0 +else + ph_rc=$? +fi + +test_status=0 +if [ "${ph_rc}" -ne 0 ]; then + printf '%s\n' "FAIL: print-policy exited ${ph_rc} (directive rejected as invalid characters)." >&2 + test_status=1 +fi +if printf '%s\n' "${policy_output}" | grep --quiet --fixed-strings -- "${ordinary_file}"$'\t'; then + printf '%s\n' "PASS: whitelists_disable_all=true accepted; ordinary entry present." +else + printf '%s\n' "FAIL: ordinary entry '${ordinary_file}' missing from print-policy output." >&2 + test_status=1 +fi + +if [ "${test_status}" -ne 0 ]; then + printf '%s\n' "----- print-policy output -----" >&2 + printf '%s\n' "${policy_output}" >&2 +fi +exit "${test_status}" diff --git a/ci/tests/permission_hardener/test_whitespace_filename.sh b/ci/tests/permission_hardener/test_whitespace_filename.sh new file mode 100755 index 00000000..6d2a63b6 --- /dev/null +++ b/ci/tests/permission_hardener/test_whitespace_filename.sh @@ -0,0 +1,138 @@ +#!/bin/bash +## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## AI-Assisted + +## WARNING: Not human-reviewed, MUST be moved to dist-ai. + +## Regression test: permission-hardener must parse a config filename that +## contains spaces. +## +## load_state() recovers the filename by reading the option fields from the +## right (a trailing whitelist keyword, or the mode/owner/group[/capability] +## tail anchored by the octal mode). A space in the filename must NOT split it +## into the wrong fields -- that drops the entry silently, leaving a SUID +## binary un-hardened. +## +## Drives the REAL script via 'print-policy' with a mode-form entry +## ( ) whose filename contains a space, and +## asserts the recovered filename appears in the printed policy. +## +## Requires root: writes a temporary config under /etc/permission-hardener.d/ +## and needs helper-scripts installed (sourced by permission-hardener). + +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)" +ph_bin="${PERMISSION_HARDENER_BIN:-${repo_root}/usr/bin/permission-hardener#security-misc-shared}" + +if [ ! -f "${ph_bin}" ]; then + printf '%s\n' "FAIL: permission-hardener not found at '${ph_bin}'." >&2 + exit 1 +fi + +test_dir="$(mktemp -d -t ph-ws-test.XXXXXX)" +## The space in the directory name is the property under test. +spaced_file="${test_dir}/some space/binary" +mkdir -p -- "${test_dir}/some space" +touch -- "${spaced_file}" + +## A filename whose space-split yields a BARE octal chunk ('744'). The mode +## must be anchored from the RIGHT: a left-to-right scan anchors on that chunk +## instead of the real mode at the end, fails the field-count check, and aborts +## the whole hardener (exit 200) rather than parsing the entry. +octal_chunk_file="${test_dir}/a 744 name" +touch -- "${octal_chunk_file}" + +## A space-free filename with a 4-option (capability) tail whose OWNER is a +## numeric id that looks octal ('0755'). Right-anchoring must NOT treat that +## owner as the mode: it would fold the real mode into the filename, the folded +## path does not exist, and the entry is silently dropped -- leaving the file +## un-hardened. The line is already well formed, so no recovery must happen. +numeric_owner_file="${test_dir}/numowner" +touch -- "${numeric_owner_file}" + +## The mirror-image of the numeric-owner case: a SPACED filename whose second +## component (field index 1) is a bare octal, with a 3-field tail. Here field 1 +## is NOT the mode, so recovery must happen. Existence disambiguates: the spaced +## name exists, its no-recovery prefix does not. +octal_second_file="${test_dir}/a 744" +touch -- "${octal_second_file}" + +## Six fields: a SPACED filename plus a numeric octal-looking owner and a +## capability tail. The rightmost octal candidate (field_count-3) is the owner, +## not the mode; anchoring there folds the mode into the filename and drops the +## entry. Existence must select field_count-4 (the real mode) instead. +numeric_owner_spaced_file="${test_dir}/x y" +touch -- "${numeric_owner_spaced_file}" + +config_dir="/etc/permission-hardener.d" +config_file="${config_dir}/zz-ai-whitespace-regression-test.conf" +mkdir -p -- "${config_dir}" + +## print-policy captures each file's current mode into this DB (must match +## store_dir in the script under test); remove the entries so a test run does +## not leave stale root-owned overrides pointing at deleted temp files. +existing_mode_admindir='/var/lib/permission-hardener-v2/existing_mode' + +## invoked indirectly via 'trap ... EXIT' +# shellcheck disable=SC2317 +cleanup() { + local leaked_file + for leaked_file in "${spaced_file}" "${octal_chunk_file}" \ + "${numeric_owner_file}" "${octal_second_file}" \ + "${numeric_owner_spaced_file}"; do + dpkg-statoverride --admindir "${existing_mode_admindir}" --remove \ + "${leaked_file}" >/dev/null 2>&1 || true + done + safe-rm -f -- "${config_file}" + safe-rm -rf -- "${test_dir}" +} +trap cleanup EXIT + +## mode-form entries: [capability] +{ + printf '%s\n' "${spaced_file} 0744 root root" + printf '%s\n' "${octal_chunk_file} 0744 root root" + printf '%s\n' "${numeric_owner_file} 0744 0755 root cap_net_raw" + printf '%s\n' "${octal_second_file} 0644 root root" + printf '%s\n' "${numeric_owner_spaced_file} 0744 0755 root cap_net_raw" +} > "${config_file}" + +## A parse failure on any line aborts the whole run with exit 200; capture the +## status via an 'if' condition (errexit stays enabled) so the failure is +## reported cleanly rather than aborting the test. +if policy_output="$( "${ph_bin}" print-policy 2>/dev/null )"; then + ph_rc=0 +else + ph_rc=$? +fi + +test_status=0 +if [ "${ph_rc}" -ne 0 ]; then + printf '%s\n' "FAIL: print-policy exited ${ph_rc} (parse aborted the whole run)." >&2 + test_status=1 +fi +## print-policy prints tab-separated columns (FileUser...), so match +## each filename as the whole first field (trailing TAB). A bare substring match +## would let '/a 744' spuriously match the '/a 744 name' entry. +for expected_file in "${spaced_file}" "${octal_chunk_file}" "${numeric_owner_file}" "${octal_second_file}" "${numeric_owner_spaced_file}"; do + if printf '%s\n' "${policy_output}" | grep --quiet --fixed-strings -- "${expected_file}"$'\t'; then + printf '%s\n' "PASS: filename '${expected_file}' parsed and present in policy." + else + printf '%s\n' "FAIL: filename '${expected_file}' missing from print-policy output." >&2 + test_status=1 + fi +done + +if [ "${test_status}" -ne 0 ]; then + printf '%s\n' "----- print-policy output -----" >&2 + printf '%s\n' "${policy_output}" >&2 +fi +exit "${test_status}" diff --git a/debian/changelog b/debian/changelog index e4f43ab1..3d05dca1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,51 @@ +security-misc (3:54.2-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Wed, 30 Sep 2026 08:33:58 +0000 + +security-misc (3:54.1-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Tue, 29 Sep 2026 08:08:57 +0000 + +security-misc (3:54.0-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Sat, 19 Sep 2026 14:09:17 +0000 + +security-misc (3:53.9-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Tue, 15 Sep 2026 14:32:35 +0000 + +security-misc (3:53.8-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Tue, 15 Sep 2026 12:00:54 +0000 + +security-misc (3:53.7-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Wed, 09 Sep 2026 11:05:43 +0000 + +security-misc (3:53.6-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Tue, 08 Sep 2026 15:30:04 +0000 + +security-misc (3:53.5-1) unstable; urgency=medium + + * New upstream version (local package). + + -- Patrick Schleizer Sun, 06 Sep 2026 16:58:23 +0000 + security-misc (3:53.4-1) unstable; urgency=medium * New upstream version (local package). diff --git a/debian/control b/debian/control index 4a944d1b..1673ab33 100644 --- a/debian/control +++ b/debian/control @@ -30,11 +30,11 @@ Rules-Requires-Root: no ## > security-misc is to be removed Package: security-misc-shared Architecture: all +Pre-Depends: helper-scripts Depends: adduser, apparmor-profile-dist, build-essential, dmsetup, - helper-scripts, libcap2-bin, libdbus-1-dev, libglib2.0-bin, diff --git a/debian/security-misc-shared.install b/debian/security-misc-shared.install index 8e72deee..b141aba2 100755 --- a/debian/security-misc-shared.install +++ b/debian/security-misc-shared.install @@ -71,7 +71,6 @@ usr/lib/permission-hardener.d/25_default_whitelist_passwd.conf#security-misc-sha usr/lib/permission-hardener.d/25_default_whitelist_policykit.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_policykit.conf usr/lib/permission-hardener.d/25_default_whitelist_postfix.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_postfix.conf usr/lib/permission-hardener.d/25_default_whitelist_qubes.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_qubes.conf -usr/lib/permission-hardener.d/25_default_whitelist_selinux.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_selinux.conf usr/lib/permission-hardener.d/25_default_whitelist_spice.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_spice.conf usr/lib/permission-hardener.d/25_default_whitelist_ssh.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_ssh.conf usr/lib/permission-hardener.d/25_default_whitelist_sudo.conf#security-misc-shared => /usr/lib/permission-hardener.d/25_default_whitelist_sudo.conf @@ -109,6 +108,7 @@ usr/lib/systemd/user/usbguard-notifier.service.d/30_security-misc.conf#security- usr/lib/udev/rules.d/95-emerg-shutdown.rules#security-misc-shared => /usr/lib/udev/rules.d/95-emerg-shutdown.rules usr/libexec/security-misc/askpass#security-misc-shared => /usr/libexec/security-misc/askpass usr/libexec/security-misc/block-unsafe-logins#security-misc-shared => /usr/libexec/security-misc/block-unsafe-logins +usr/libexec/security-misc/build-emerg-shutdown#security-misc-shared => /usr/libexec/security-misc/build-emerg-shutdown usr/libexec/security-misc/build-fm-shim-backend#security-misc-shared => /usr/libexec/security-misc/build-fm-shim-backend usr/libexec/security-misc/check-for-usb-controller#security-misc-shared => /usr/libexec/security-misc/check-for-usb-controller usr/libexec/security-misc/compile-emerg-shutdown#security-misc-shared => /usr/libexec/security-misc/compile-emerg-shutdown diff --git a/debian/security-misc-shared.postinst b/debian/security-misc-shared.postinst index af81bf2a..0b1567c7 100755 --- a/debian/security-misc-shared.postinst +++ b/debian/security-misc-shared.postinst @@ -3,6 +3,9 @@ ## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. +## style-ok: no-has - has.bsh isn't reliably available in maintainer scripts +## style-ok: no-strict - full strict block could leak into APT/debconf + if [ -f /usr/libexec/helper-scripts/pre.bsh ]; then source /usr/libexec/helper-scripts/pre.bsh fi @@ -11,11 +14,11 @@ fi ## the postinst itself does not use debconf commands. source /usr/share/debconf/confmodule -set -e +set -o errexit true " ##################################################################### -## INFO: BEGIN: $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME $* +## INFO: BEGIN: ${DPKG_MAINTSCRIPT_PACKAGE} ${DPKG_MAINTSCRIPT_NAME} $* ##################################################################### " @@ -27,16 +30,16 @@ permission_hardening_legacy_config_folder() { } permission_hardening() { - echo "Running SUID Disabler and Permission Hardener... See also:" - echo "https://www.kicksecure.com/wiki/SUID_Disabler_and_Permission_Hardener" - echo "$0: INFO: running: permission-hardener enable" + printf '%s\n' "Running SUID Disabler and Permission Hardener... See also:" + printf '%s\n' "https://www.kicksecure.com/wiki/SUID_Disabler_and_Permission_Hardener" + printf '%s\n' "$0: INFO: running: permission-hardener enable" ## By design: report error but return 0 to avoid breaking APT. ## A failing postinst would leave the package in a broken state. if ! permission-hardener enable ; then - echo "$0: ERROR: Permission hardening failed." >&2 + printf '%s\n' "$0: ERROR: Permission hardening failed." >&2 return 0 fi - echo "$0: INFO: Permission hardening success." + printf '%s\n' "$0: INFO: Permission hardening success." } fix_pkexec_remembered_permissions() { @@ -116,7 +119,7 @@ root root 4755 /usr/bin/mount root root 644 /etc/issue root root 755 /etc/cron.d" - printf '%s\n' "$state_str" | tee /var/lib/permission-hardener-v2/existing_mode/statoverride + printf '%s\n' "${state_str}" | tee /var/lib/permission-hardener-v2/existing_mode/statoverride touch "/var/lib/security-misc/do_once/${FUNCNAME[0]}_version_1" } @@ -125,6 +128,10 @@ build_fm_shim_backend() { /usr/libexec/security-misc/build-fm-shim-backend } +build_emerg_shutdown() { + /usr/libexec/security-misc/build-emerg-shutdown +} + case "$1" in configure) if [ -d /etc/skel/.gnupg ]; then @@ -164,13 +171,16 @@ case "$1" in ## Build the org.freedesktop.FileManager1 shim backend. build_fm_shim_backend + + ## Pre-compile emerg-shutdown. + build_emerg_shutdown ;; abort-upgrade|abort-remove|abort-deconfigure) ;; triggered) - echo "INFO: triggered $DPKG_MAINTSCRIPT_PACKAGE: '$DPKG_MAINTSCRIPT_PACKAGE' $DPKG_MAINTSCRIPT_PACKAGE DPKG_MAINTSCRIPT_NAME: '$DPKG_MAINTSCRIPT_NAME' $\*: '$*' 2: '$2'" + printf '%s\n' "INFO: triggered ${DPKG_MAINTSCRIPT_PACKAGE}: '${DPKG_MAINTSCRIPT_PACKAGE}' ${DPKG_MAINTSCRIPT_PACKAGE} DPKG_MAINTSCRIPT_NAME: '${DPKG_MAINTSCRIPT_NAME}' $\*: '$*' 2: '$2'" /usr/share/security-misc/lkrg/lkrg-virtualbox || true /usr/libexec/security-misc/mmap-rnd-bits || true permission_hardening @@ -178,7 +188,7 @@ case "$1" in ;; *) - echo "$DPKG_MAINTSCRIPT_NAME called with unknown argument \`$1'" >&2 + printf '%s\n' "${DPKG_MAINTSCRIPT_NAME} called with unknown argument \`$1'" >&2 exit 1 ;; esac @@ -194,8 +204,8 @@ permission_hardening ## https://bugs.debian.org/481542 if command -v update-grub >/dev/null 2>&1; then update-grub || \ - echo "$DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME ERROR: Running \ -'update-grub' failed with exit code $?. $DPKG_MAINTSCRIPT_PACKAGE is most \ + printf '%s\n' "${DPKG_MAINTSCRIPT_PACKAGE} ${DPKG_MAINTSCRIPT_NAME} ERROR: Running \ +'update-grub' failed with exit code $?. ${DPKG_MAINTSCRIPT_PACKAGE} is most \ likely only the trigger, not the cause. Unless you know this is not an issue, \ you should fix running 'update-grub', otherwise your system might no longer \ boot." >&2 @@ -213,7 +223,7 @@ permission_hardening_legacy_config_folder true " ##################################################################### -## INFO: END : $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME $* +## INFO: END : ${DPKG_MAINTSCRIPT_PACKAGE} ${DPKG_MAINTSCRIPT_NAME} $* ##################################################################### " diff --git a/debian/security-misc-shared.postrm b/debian/security-misc-shared.postrm index 13dc5884..0be096ef 100755 --- a/debian/security-misc-shared.postrm +++ b/debian/security-misc-shared.postrm @@ -16,7 +16,9 @@ true " " ## https://forums.whonix.org/t/is-security-misc-suitable-for-hardening-bridges-and-relays/8299/11 -pam-auth-update --package --remove "$DPKG_MAINTSCRIPT_PACKAGE" +if command -v pam-auth-update >/dev/null 2>&1; then + pam-auth-update --package +fi rm -f /etc/sysctl.d/30_security-misc_aslr-mmap.conf diff --git a/debian/security-misc-shared.preinst b/debian/security-misc-shared.preinst index 93d8b2f5..43a07ff6 100755 --- a/debian/security-misc-shared.preinst +++ b/debian/security-misc-shared.preinst @@ -3,15 +3,22 @@ ## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. -if [ -f /usr/libexec/helper-scripts/pre.bsh ]; then - source /usr/libexec/helper-scripts/pre.bsh -fi - -set -e +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose +export LC_ALL=C + +## helper-scripts is a Pre-Depends. +source /usr/libexec/helper-scripts/pre.bsh +source /usr/libexec/helper-scripts/has.bsh +source /usr/libexec/helper-scripts/accountctl.sh true " ##################################################################### -## INFO: BEGIN: $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME $@ +## INFO: BEGIN: ${DPKG_MAINTSCRIPT_PACKAGE:-} ${DPKG_MAINTSCRIPT_NAME:-} $* ##################################################################### " @@ -60,66 +67,43 @@ user_groups_modifications() { } output_skip_checks() { - echo "security-misc-shared '$0' INFO: Allow installation of security-misc-shared anyway." >&2 - echo "security-misc-shared '$0' INFO: (technical reason: $@)" >&2 - echo "security-misc-shared '$0' INFO: If this is a chroot this is probably OK." >&2 - echo "security-misc-shared '$0' INFO: Otherwise you might not be able to login." >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: Allow installation of security-misc-shared anyway." >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: (technical reason: $*)" >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: If this is a chroot this is probably OK." >&2 + printf '%s\n' "security-misc-shared '${0}' INFO: Otherwise you might not be able to login." >&2 } sudo_users_check () { - if command -v "qubesdb-read" &>/dev/null; then + if has qubesdb-read; then ## Qubes users can use dom0 to get a root terminal emulator. ## For example: ## qvm-run -u root debian-10 xterm return 0 fi - local sudo_users user_with_sudo are_there_any_sudo_users OLD_IFS - - sudo_users="$(getent group sudo | cut -d: -f4)" - ## example sudo_users: - ## user,root - - OLD_IFS="$IFS" - IFS="," - export IFS - - for user_with_sudo in $sudo_users ; do - if [ "$user_with_sudo" = "root" ]; then - ## root login is also restricted. - ## Therefore user "root" being member of group "sudo" is - ## considered insufficient. - continue - fi - are_there_any_sudo_users=yes - break - done - - IFS="$OLD_IFS" - export IFS - - if [ "$are_there_any_sudo_users" = "yes" ]; then + if group_has_nonroot_member sudo; then return 0 fi ## Prevent users from locking themselves out. ## https://forums.whonix.org/t/is-security-misc-suitable-for-hardening-bridges-and-relays/8299/4 - echo "$0: ERROR: No account is a member of group 'sudo'. Installation aborted." >&2 - echo "$0: ERROR: You probably want to run:" >&2 - echo "$0: NOTE: Replace account 'user' with your actual Linux user account name." >&2 - echo "" >&2 - echo "sudo adduser user sudo" >&2 - echo "sudo adduser user console" >&2 - echo "" >&2 - echo "$0: ERROR: See also installation instructions:" >&2 - echo "https://www.kicksecure.com/wiki/security-misc#install" >&2 - - if [ "$SECURITY_MISC_INSTALL" = "force" ]; then + printf '%s\n' "${0}: ERROR: No account is a member of group 'sudo'. Installation aborted." >&2 + printf '%s\n' "${0}: ERROR: You probably want to run:" >&2 + printf '%s\n' "${0}: NOTE: Replace account 'user' with your actual Linux user account name." >&2 + printf '%s\n' "" >&2 + printf '%s\n' "sudo adduser user sudo" >&2 + printf '%s\n' "sudo adduser user console" >&2 + printf '%s\n' "" >&2 + printf '%s\n' "${0}: ERROR: See also installation instructions:" >&2 + printf '%s\n' "https://www.kicksecure.com/wiki/security-misc#install" >&2 + + if [ "${SECURITY_MISC_INSTALL:-}" = "force" ]; then output_skip_checks "Environment variable SECURITY_MISC_INSTALL is set to 'force'." return 0 fi - if test -f "/var/lib/security-misc/skip_install_check" ; then - output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists." + if test -f "/var/lib/security-misc/skip_install_check" \ + || [ "${dist_build_iso:-}" = "true" ] ; then + output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists or this is an ISO/live build (dist_build_iso=true)." return 0 fi @@ -127,64 +111,42 @@ sudo_users_check () { } console_users_check() { - if [ "$SECURITY_MISC_INSTALL" = "force" ]; then + if [ "${SECURITY_MISC_INSTALL:-}" = "force" ]; then return 0 fi - if test -f "/var/lib/security-misc/skip_install_check" ; then + if test -f "/var/lib/security-misc/skip_install_check" \ + || [ "${dist_build_iso:-}" = "true" ] ; then return 0 fi - if command -v "qubesdb-read" &>/dev/null; then + if has qubesdb-read; then ## Qubes users can use dom0 to get a root terminal emulator. ## For example: ## qvm-run -u root debian-10 xterm return 0 fi - local console_users console_unrestricted_users user_with_console are_there_any_console_users OLD_IFS - - console_users="$(getent group console | cut -d: -f4)" - ## example console_users: - ## user - console_unrestricted_users="$(getent group console-unrestricted | cut -d: -f4)" - - OLD_IFS="$IFS" - IFS="," - export IFS - - for user_with_console in $console_users $console_unrestricted_users ; do - if [ "$user_with_console" = "root" ]; then - ## root login is also restricted. - ## Therefore account "root" being member of group "console" is - ## considered insufficient. - continue - fi - are_there_any_console_users=yes - break - done - - IFS="$OLD_IFS" - export IFS - ## Prevent users from locking themselves out. ## https://forums.whonix.org/t/is-security-misc-suitable-for-hardening-bridges-and-relays/8299/4 - if [ "$are_there_any_console_users" = "yes" ]; then + if group_has_nonroot_member console \ + || group_has_nonroot_member console-unrestricted; then return 0 fi - echo "$0: ERROR: No account is a member of group 'console'. Installation aborted." >&2 - echo "$0: ERROR: You probably want to run:" >&2 - echo "" >&2 - echo "sudo adduser user console" >&2 - echo "" >&2 - echo "$0: ERROR: See also installation instructions:" >&2 - echo "https://www.whonix.org/wiki/security-misc#install" >&2 + printf '%s\n' "${0}: ERROR: No account is a member of group 'console'. Installation aborted." >&2 + printf '%s\n' "${0}: ERROR: You probably want to run:" >&2 + printf '%s\n' "" >&2 + printf '%s\n' "sudo adduser user console" >&2 + printf '%s\n' "" >&2 + printf '%s\n' "${0}: ERROR: See also installation instructions:" >&2 + printf '%s\n' "https://www.whonix.org/wiki/security-misc#install" >&2 - if [ "$SECURITY_MISC_INSTALL" = "force" ]; then + if [ "${SECURITY_MISC_INSTALL:-}" = "force" ]; then output_skip_checks "Environment variable SECURITY_MISC_INSTALL is set to 'force'." return 0 fi - if test -f "/var/lib/security-misc/skip_install_check" ; then - output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists." + if test -f "/var/lib/security-misc/skip_install_check" \ + || [ "${dist_build_iso:-}" = "true" ] ; then + output_skip_checks "File '/var/lib/security-misc/skip_install_check' exists or this is an ISO/live build (dist_build_iso=true)." return 0 fi @@ -192,7 +154,7 @@ console_users_check() { } legacy() { - if [ -f "/var/lib/legacy/do_once/${FUNCNAME}_version_1" ]; then + if [ -f "/var/lib/legacy/do_once/${FUNCNAME[0]}_version_1" ]; then return 0 fi @@ -205,11 +167,11 @@ legacy() { continue_yes=true fi - if [ ! "$continue_yes" = "true" ]; then + if [ ! "${continue_yes:-}" = "true" ]; then return 0 fi - if command -v "qubesdb-read" &>/dev/null; then + if has qubesdb-read; then ## Qubes users can use dom0 to get a root terminal emulator. ## For example: ## qvm-run -u root debian-10 xterm @@ -220,23 +182,23 @@ legacy() { user_to_be_created=user - if ! id "$user_to_be_created" &>/dev/null ; then - true "INFO: Account '$user_to_be_created' does not exist. Skipping adding account '$user_to_be_created' to group 'console' and also skipping 'pam-auth-update --enable console-lockdown-security-misc'." + if ! id "${user_to_be_created}" &>/dev/null ; then + true "INFO: Account '${user_to_be_created}' does not exist. Skipping adding account '${user_to_be_created}' to group 'console' and also skipping 'pam-auth-update --enable console-lockdown-security-misc'." return 0 fi - adduser "$user_to_be_created" console + adduser "${user_to_be_created}" console pam-auth-update --enable console-lockdown-security-misc mkdir --parents "/var/lib/legacy/do_once" - touch "/var/lib/legacy/do_once/${FUNCNAME}_version_1" + touch "/var/lib/legacy/do_once/${FUNCNAME[0]}_version_1" } user_groups_modifications legacy -if [ "$1" = "install" ] || [ "$1" = "upgrade" ]; then +if [ "${1:-}" = "install" ] || [ "${1:-}" = "upgrade" ]; then sudo_users_check console_users_check fi @@ -249,7 +211,7 @@ true "INFO: Done with debhelper." true " ##################################################################### -## INFO: END : $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME $@ +## INFO: END : ${DPKG_MAINTSCRIPT_PACKAGE:-} ${DPKG_MAINTSCRIPT_NAME:-} $* ##################################################################### " diff --git a/debian/security-misc-shared.prerm b/debian/security-misc-shared.prerm index 1c4cd871..a4106c3e 100755 --- a/debian/security-misc-shared.prerm +++ b/debian/security-misc-shared.prerm @@ -16,7 +16,17 @@ true " " if [ "$1" = remove ]; then - pam-auth-update --package --remove "$DPKG_MAINTSCRIPT_PACKAGE" + ## REMINDER: This list of policies must be kept in sync with the contents of + ## usr/share/pam-configs. + pam-auth-update --package --remove \ + block-unsafe-logins-security-misc \ + console-lockdown-security-misc \ + faillock-preauth-security-misc \ + mkhomedir-security-misc \ + pam-abort-on-locked-password-security-misc \ + umask-security-misc \ + unix-faillock-security-misc \ + wheel-security-misc fi true "INFO: debhelper beginning here." diff --git a/etc/default/grub.d/40_kernel_hardening.cfg#security-misc-shared b/etc/default/grub.d/40_kernel_hardening.cfg#security-misc-shared index 498a6ddc..ecbddc7a 100644 --- a/etc/default/grub.d/40_kernel_hardening.cfg#security-misc-shared +++ b/etc/default/grub.d/40_kernel_hardening.cfg#security-misc-shared @@ -55,7 +55,7 @@ GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX slab_nomerge" ## KSPP=partial ## KSPP sets the kernel parameters and CONFIG_SLUB_DEBUG. ## -## TODO: Debian forky / 14 +## REMINDER: Debian forky / 14 ## The first parameter is applicable when using Linux kernel >= 6.17 (retained here for future-proofing and completeness). ## #GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX hash_pointers=always" diff --git a/etc/ssh/ssh_config.d/30_security-misc.conf#security-misc-shared b/etc/ssh/ssh_config.d/30_security-misc.conf#security-misc-shared index b582dd12..67ad1e86 100644 --- a/etc/ssh/ssh_config.d/30_security-misc.conf#security-misc-shared +++ b/etc/ssh/ssh_config.d/30_security-misc.conf#security-misc-shared @@ -1,9 +1,9 @@ ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. -## Don't edit this file, to overwrite any options, edit a file with a higher -## number that is read later by SSH, such as -## '/etc/ssh/ssh_config.d/50_user.conf'. If your configuration changes do not +## Don't edit this file; to overwrite any options, edit a file with a higher +## number that is read earlier by SSH, such as +## '/etc/ssh/ssh_config.d/10_user.conf'. If your configuration changes do not ## need to be system-wide, you may also consider placing overrides in ## ~/.ssh/config. @@ -12,11 +12,13 @@ Host * VisualHostKey yes + ## Ciphers that are Terrapin-vulnerable when an outdated client or server are + ## used are intentionally retained. Connecting to a Terrapin-vulnerable + ## server cannot be made secure (as any Terrapin-vulnerable server will be + ## vulnerable to other issues as well) and is therefore unsupported. Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr MACs umac-128-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com - KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256,curve25519-sha256,curve25519-sha256@libssh.org - ## To force the use of quantum-resistant key exchange algorithms, override - ## the above with - # KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256 + ## Only post-quantum-sequre KexAlgorithms are allowed. + KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256 HostKeyAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519 PubkeyAcceptedAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519 diff --git a/etc/ssh/sshd_config.d/30_security-misc.conf#security-misc-shared b/etc/ssh/sshd_config.d/30_security-misc.conf#security-misc-shared index 45496fd5..6d52003d 100644 --- a/etc/ssh/sshd_config.d/30_security-misc.conf#security-misc-shared +++ b/etc/ssh/sshd_config.d/30_security-misc.conf#security-misc-shared @@ -1,9 +1,9 @@ ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. -## Don't edit this file, to overwrite any options, edit a file with a higher -## number that is read later by SSHD, such as -## '/etc/ssh/sshd_config.d/50_user.conf'. +## Don't edit this file; to overwrite any options, edit a file with a lower +## number that is read earlier by sshd, such as +## '/etc/ssh/sshd_config.d/10_user.conf'. ## See also: ## https://www.kicksecure.com/wiki/SSH#Server_Configuration_File @@ -15,12 +15,14 @@ MaxAuthTries 3 HostKey /etc/ssh/ssh_host_ed25519_key HostKeyAlgorithms ssh-ed25519 PubkeyAcceptedAlgorithms ssh-ed25519,sk-ssh-ed25519@openssh.com +## Ciphers that are Terrapin-vulnerable when an outdated client or server are +## used are intentionally retained. Connections from a Terrapin-vulnerable +## client cannot be made secure (as any Terrapin-vulnerable client will be +## vulnerable to other issues as well) and is therefore unsupported. Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com MACs umac-128-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com -KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256,curve25519-sha256,curve25519-sha256@libssh.org -## To force the use of quantum-resistant key exchange algorithms, override the -## above with -# KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256 +## Only post-quantum-sequre KexAlgorithms are allowed. +KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,mlkem768x25519-sha256 ## Override with 'no' to fully deny root login, or leave this as ## 'prohibit-password' for denying root password login but still allowing diff --git a/usr/bin/fm-shim-frontend#security-misc-shared b/usr/bin/fm-shim-frontend#security-misc-shared index 899465f5..fadcd9e0 100755 --- a/usr/bin/fm-shim-frontend#security-misc-shared +++ b/usr/bin/fm-shim-frontend#security-misc-shared @@ -1,7 +1,10 @@ -#!/usr/bin/python3 -u +#!/usr/bin/python3 -Bsu ## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. +## Shell-invocation guard. +"exec" "bash" "-c" "printf '%s\n' '$0: ERROR: Do not execute this script with bash!' >&2; exit 1" + from fm_shim_frontend.fm_shim_frontend import main main() diff --git a/usr/bin/permission-hardener#security-misc-shared b/usr/bin/permission-hardener#security-misc-shared index 9f9f351c..7e92e0a5 100755 --- a/usr/bin/permission-hardener#security-misc-shared +++ b/usr/bin/permission-hardener#security-misc-shared @@ -14,7 +14,13 @@ ## works very well for literal matching, and it is used that way extensively ## throughout this script. -set -o errexit -o nounset -o pipefail +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose +export LC_ALL=C ## Constants # shellcheck disable=SC2034 @@ -26,12 +32,12 @@ dpkg_admindir_parameter_new_mode="--admindir ${store_dir}/new_mode" delimiter="#permission-hardener-delimiter#" ## Library imports -# shellcheck disable=SC1091 -source /usr/libexec/helper-scripts/safe_echo.sh -# shellcheck disable=SC1091 -source /usr/libexec/helper-scripts/log_run_die.sh -# shellcheck disable=SC1091 -source /usr/libexec/helper-scripts/strings.bsh +# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/safe_echo.sh +source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/safe_echo.sh +# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/log_run_die.sh +source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/log_run_die.sh +# shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/strings.bsh +source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/strings.bsh ## Functions echo_wrapper_ignore() { @@ -115,7 +121,7 @@ output_stat() { return 1 fi - if [ -z "$stat_output" ]; then + if [ -z "${stat_output}" ]; then log error "stat_output is empty. File name: '${file_name}' Stat output: '${stat_output}' @@ -166,7 +172,7 @@ hardlink_count: '${hardlink_count}' return 1 fi - if [ "$file_name" != "$file_name_from_stat" ]; then + if [ "${file_name}" != "${file_name_from_stat}" ]; then log error "\ File name is different from file name received from stat: File name: '${file_name}' @@ -279,7 +285,7 @@ check_nosuid_whitelist() { ## literal matching is intentional here too [[ " ${policy_exact_white_list[*]} " =~ " ${target_file} " ]] && return 1 - for match_white_list_entry in ${policy_match_white_list[@]+"${policy_match_white_list[@]}"}; do + for match_white_list_entry in "${policy_match_white_list[@]}"; do if [[ "${target_file}" == *"${match_white_list_entry}"* ]]; then return 1 fi @@ -395,21 +401,21 @@ load_late_nosuid_policy() { } load_state_without_policy() { - local line field_list + local line sf_owner sf_group sf_mode sf_path - ## Load the state file from disk + ## Load the state file from disk. if [ -f "${state_file}" ]; then while read -r line; do - read -r -a field_list <<< "${line}" - if (( ${#field_list[@]} != 4 )); then + read -r sf_owner sf_group sf_mode sf_path <<< "${line}" + if [ -z "${sf_path}" ]; then log info \ "Invalid number of fields in state file line: '${line}'. Skipping." continue fi - state_user_owner_list+=( "${field_list[0]}" ) - state_group_owner_list+=( "${field_list[1]}" ) - state_mode_list+=( "${field_list[2]}" ) - state_file_list+=( "${field_list[3]}" ) + state_user_owner_list+=( "${sf_owner}" ) + state_group_owner_list+=( "${sf_group}" ) + state_mode_list+=( "${sf_mode}" ) + state_file_list+=( "${sf_path}" ) done < "${state_file}" fi } @@ -418,13 +424,14 @@ load_state() { ## Config format: ## path options ## where options is one of: - ## user_owner group_owner filemode [capability-setting] + ## filemode user_owner group_owner [capability-setting] ## [nosuid|exactwhitelist|matchwhitelist|disablewhitelist] ## ## Additionally, the special value 'whitelists_disable_all=true' is understood ## to mean that all whitelisting should be ignored. local config_file line field_list policy_nosuid_file_item policy_file_item + local one_field field_count fso_name meta_list field_index hit_barrier ## Load configuration, deferring whitelist handling until later for config_file in \ @@ -448,6 +455,12 @@ load_state() { continue fi + if [ "${line}" = 'whitelists_disable_all=true' ]; then + whitelists_disable_all=true + log info "whitelists_disable_all=true" + continue + fi + if ! [[ "${line}" =~ ^[-0-9a-zA-Z._/[:space:]]*$ ]]; then exit_code=200 log error "Line contains invalid characters: '${line}'" >&2 @@ -456,18 +469,52 @@ load_state() { exit "${exit_code}" fi - if [ "${line}" = 'whitelists_disable_all=true' ]; then - whitelists_disable_all=true - log info "whitelists_disable_all=true" - continue - fi - processed_config_line="${line}" - IFS=' ' read -r -a field_list <<< "${line}" - - case "${#field_list[@]}" in - 2|4|5) true;; + ## We can handle filenames with spaces. This is because the first item + ## after a filename is either an octal file mode or a keyword, and we + ## only support user/group names, not UIDs and GIDs, as owner + ## identifiers. This means that we can scan from right to left for the + ## furthest-right octal integer or keyword field and use it to separate + ## the filename from the rest of the options. (If usernames could + ## themselves be octal integers, this would break, but adduser.conf's + ## default NAME_REGEX forbids this. This is definitely a hack, but the + ## alternative is to change the format of the policy files, which would + ## break custom config for users. + ## + ## Do not attempt to reimplement this in a way that copes with usernames + ## that look identical to UIDs. Systems containing such usernames are + ## very broken and unsupported. + ## + ## 'read' will collapse consecutive spaces when splitting on spaces. + ## Convert spaces into newlines first, then split on newlines with a + ## loop. + field_list=() + while IFS= read -r one_field; do + field_list+=( "${one_field}" ) + done < <(printf '%s\n' "${line}" | tr ' ' '\n') + field_count="${#field_list[@]}" + + hit_barrier='false' + fso_name="" + meta_list=() + for (( field_index = field_count - 1; field_index >= 0; \ + field_index-- )); do + if [ "${hit_barrier}" = 'false' ]; then + if [[ "${field_list[field_index]}" =~ ^[0-7]{3,4}$ ]] \ + || [[ "${field_list[field_index]}" =~ ^(nosuid|exactwhitelist|matchwhitelist|disablewhitelist)$ ]]; then + hit_barrier='true' + fi + meta_list=( "${field_list[field_index]}" "${meta_list[@]}" ) + else + fso_name="${field_list[field_index]}${fso_name:+" ${fso_name}"}" + fi + done + + case "${#meta_list[@]}" in + 1|3|4) + true + ;; *) exit_code=200 log error "Line contains an invalid number of fields: '${line}'" >&2 @@ -476,29 +523,29 @@ load_state() { esac # Strip trailing slash if appropriate - field_list[0]="${field_list[0]%/}" + fso_name="${fso_name%/}" - case "${field_list[1]}" in + case "${meta_list[0]}" in 'exactwhitelist') - [ ! -e "${field_list[0]}" ] && continue - policy_exact_white_list+=( "${field_list[0]}" ) + [ ! -e "${fso_name}" ] && continue + policy_exact_white_list+=( "${fso_name}" ) continue ;; 'matchwhitelist') - policy_match_white_list+=( "${field_list[0]}" ) + policy_match_white_list+=( "${fso_name}" ) continue ;; 'disablewhitelist') - policy_disable_white_list+=( "${field_list[0]}" ) + policy_disable_white_list+=( "${fso_name}" ) continue ;; 'nosuid') - [ ! -e "${field_list[0]}" ] && continue - policy_nosuid_file_list+=( "${field_list[0]}" ) + [ ! -e "${fso_name}" ] && continue + policy_nosuid_file_list+=( "${fso_name}" ) ;; *) - [ ! -e "${field_list[0]}" ] && continue - add_to_policy "${field_list[@]}" + [ ! -e "${fso_name}" ] && continue + add_to_policy "${fso_name}" "${meta_list[@]}" ;; esac done < "${config_file}" @@ -695,10 +742,10 @@ undo_policy_for_file() { undo_file="${1}" undo_all=false - verbose='--verbose' + verbose=('--verbose') if [ "${undo_file}" = 'all' ]; then undo_all=true - verbose='' + verbose=() fi if [ ! -f "${state_file}" ]; then @@ -737,12 +784,10 @@ undo_policy_for_file() { state_user_owner_item="${state_user_owner_list[state_idx]}" state_group_owner_item="${state_group_owner_list[state_idx]}" state_mode_item="${state_mode_list[state_idx]}" - # shellcheck disable=SC2086 - chown ${verbose} -- "${state_user_owner_item}:${state_group_owner_item}" \ + chown "${verbose[@]}" -- "${state_user_owner_item}:${state_group_owner_item}" \ "${undo_file}" || exit_code=202 ## chmod needs to be run after chown since chown removes suid. - # shellcheck disable=SC2086 - chmod ${verbose} "${state_mode_item}" "${undo_file}" || exit_code=203 + chmod "${verbose[@]}" "${state_mode_item}" "${undo_file}" || exit_code=203 else log info "File does not exist: '${undo_file}'" fi @@ -788,6 +833,7 @@ print_columns() { done format_str="${format_str}\n" ## Using a dynamically generated format string on purpose. + ## style-ok: printf-format # shellcheck disable=SC2059 printf "${format_str}" "$@" } @@ -832,9 +878,9 @@ print_raw_policy_config() { if [ ! -f "${config_file}" ]; then continue fi - echo "*** begin ${config_file} ***" + printf '%s\n' "*** begin ${config_file} ***" cat "${config_file}" - echo "*** end ${config_file} ***" + printf '%s\n' "*** end ${config_file} ***" done } @@ -842,13 +888,13 @@ print_raw_state() { local state_file for state_file in "${store_dir}/existing_mode/statoverride" \ "${store_dir}/new_mode/statoverride"; do - echo "*** begin ${state_file} ***" + printf '%s\n' "*** begin ${state_file} ***" if [ -f "${state_file}" ]; then cat "${state_file}" else - echo '(file does not exist)' + printf '%s\n' '(file does not exist)' fi - echo "*** end ${state_file} ***" + printf '%s\n' "*** end ${state_file} ***" done } @@ -856,13 +902,13 @@ print_fs_audit() { local state_idx state_file_item state_user_owner_item state_group_owner_item \ state_mode_item - echo 'Legend:' - echo '... - Warning about an unusual, but not necessarily wrong, condition' - echo '!!! - Warning about an unusual and definitely wrong condition' - echo '*** - File permission data, actual state on filesystem is consistent with policy' - echo '^^^ - File permission data, actual state on filesystem is inconsistent with policy' - echo 'vvv - File permissions specified by state, always shown after a ^^^ item' - echo + printf '%s\n' 'Legend:' + printf '%s\n' '... - Warning about an unusual, but not necessarily wrong, condition' + printf '%s\n' '!!! - Warning about an unusual and definitely wrong condition' + printf '%s\n' '*** - File permission data, actual state on filesystem is consistent with policy' + printf '%s\n' '^^^ - File permission data, actual state on filesystem is inconsistent with policy' + printf '%s\n' 'vvv - File permissions specified by state, always shown after a ^^^ item' + printf '%s\n' '' for (( state_idx=0; state_idx < ${#state_file_list[@]}; state_idx++ )); do state_file_item="${state_file_list[state_idx]}" @@ -886,7 +932,7 @@ print_fs_audit() { output_stat "${state_file_item}" if [ -z "${file_name_from_stat}" ]; then - echo "... '${state_file_item}' does not exist or has multiple hardlinks" + printf '%s\n' "... '${state_file_item}' does not exist or has multiple hardlinks" continue fi @@ -895,20 +941,20 @@ print_fs_audit() { || [ "${existing_mode}" != "${state_mode_item}" ]; then if ! [[ $'\n'"${passwd_file_contents}" \ =~ $'\n'"${state_user_owner_item}:" ]]; then - echo "!!! Owner from config does not exist: '${state_user_owner_item}'" + printf '%s\n' "!!! Owner from config does not exist: '${state_user_owner_item}'" continue fi if ! [[ $'\n'"${group_file_contents}" \ =~ $'\n'"${state_group_owner_item}:" ]]; then - echo "!!! Group from config does not exist: '${state_group_owner_item}'" + printf '%s\n' "!!! Group from config does not exist: '${state_group_owner_item}'" continue fi - echo "^^^ ${file_name_from_stat} ${existing_owner}:${existing_group} ${existing_mode}" - echo "vvv ${file_name_from_stat} ${state_user_owner_item}:${state_group_owner_item} ${state_mode_item}" + printf '%s\n' "^^^ ${file_name_from_stat} ${existing_owner}:${existing_group} ${existing_mode}" + printf '%s\n' "vvv ${file_name_from_stat} ${state_user_owner_item}:${state_group_owner_item} ${state_mode_item}" else - echo "*** ${file_name_from_stat} ${existing_owner}:${existing_group} ${existing_mode}" + printf '%s\n' "*** ${file_name_from_stat} ${existing_owner}:${existing_group} ${existing_mode}" fi done } @@ -988,49 +1034,49 @@ case "${1:-}" in print_state ;; print-diagnostics) - echo '=== BEGIN PERMISSION-HARDENER DIAGNOSTICS ===' + printf '%s\n' '=== BEGIN PERMISSION-HARDENER DIAGNOSTICS ===' - echo '--- BEGIN State without policy ---' + printf '%s\n' '--- BEGIN State without policy ---' load_state_without_policy print_state - echo '--- END State without policy ---' + printf '%s\n' '--- END State without policy ---' reset_global_vars - echo '--- BEGIN Policy without state ---' + printf '%s\n' '--- BEGIN Policy without state ---' load_state print_policy - echo '--- END Policy without state ---' + printf '%s\n' '--- END Policy without state ---' reset_global_vars - echo '--- BEGIN Policy-applied-state ---' + printf '%s\n' '--- BEGIN Policy-applied-state ---' load_state apply_policy print_state - echo '--- END Policy-applied state ---' + printf '%s\n' '--- END Policy-applied state ---' reset_global_vars - echo '--- BEGIN Master dpkg-statoverride database ---' + printf '%s\n' '--- BEGIN Master dpkg-statoverride database ---' dpkg-statoverride --list - echo '--- END Master dpkg-statoverride database ---' + printf '%s\n' '--- END Master dpkg-statoverride database ---' - echo '--- BEGIN Raw policy configuration ---' + printf '%s\n' '--- BEGIN Raw policy configuration ---' print_raw_policy_config - echo '--- END Raw policy configuration ---' + printf '%s\n' '--- END Raw policy configuration ---' - echo '--- BEGIN Raw state data ---' + printf '%s\n' '--- BEGIN Raw state data ---' print_raw_state - echo '--- END Raw state data ---' + printf '%s\n' '--- END Raw state data ---' - echo '--- BEGIN Filesystem state audit ---' + printf '%s\n' '--- BEGIN Filesystem state audit ---' load_state apply_policy print_fs_audit - echo '--- END Filesystem state audit ---' + printf '%s\n' '--- END Filesystem state audit ---' - echo '=== END PERMISSION-HARDENER DIAGNOSTICS ===' + printf '%s\n' '=== END PERMISSION-HARDENER DIAGNOSTICS ===' ;; -h|--help) print_usage diff --git a/usr/bin/remount-secure#security-misc-shared b/usr/bin/remount-secure#security-misc-shared index 957ad46f..5d1322ab 100755 --- a/usr/bin/remount-secure#security-misc-shared +++ b/usr/bin/remount-secure#security-misc-shared @@ -26,24 +26,33 @@ ## https://www.kicksecure.com/wiki/Dev/remount-secure ## https://forums.whonix.org/t/re-mount-home-and-other-with-noexec-and-nosuid-among-other-useful-mount-options-for-better-security/7707 +## style-ok: no-has - may run in dracut without helper-scripts + +## Mount helper functions (_boot, _sys, _usr, ...) are invoked indirectly via +## the dispatch in main(). +# shellcheck disable=SC2317 + #set -x -set -e -set -o pipefail +set -o errexit set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose init() { if test -o xtrace ; then - output_command=true + output_command=( true ) else - output_command=echo + output_command=( printf '%s\n' ) fi - $output_command "$0: INFO: START" + "${output_command[@]}" "$0: INFO: START" ## dracut does not have id. Saving space in initial ramdisk. if command -v id &>/dev/null ; then if [ "$(id -u)" != "0" ]; then - $output_command "ERROR: must be run as root! sudo $0" + "${output_command[@]}" "ERROR: must be run as root! sudo $0" exit 1 fi fi @@ -53,10 +62,10 @@ init() { ## dracut sets NEWROOT=/sysroot [[ -v NEWROOT ]] || NEWROOT="" - if [ "$NEWROOT" = "" ]; then - $output_command "INFO: dracut detected: no" + if [ "${NEWROOT}" = "" ]; then + "${output_command[@]}" "INFO: dracut detected: no" else - $output_command "INFO: dracut detected: yes - NEWROOT: '$NEWROOT'" + "${output_command[@]}" "INFO: dracut detected: yes - NEWROOT: '${NEWROOT}'" fi ## Debugging. @@ -76,33 +85,33 @@ parse_options() { do case ${1:-} in 0) - $output_command "WARNING: Not using remount-secure." + "${output_command[@]}" "WARNING: Not using remount-secure." exit 0 shift ;; 1) - $output_command "INFO: level 1/3 (low)" + "${output_command[@]}" "INFO: level 1/3 (low)" most_noexec_maybe="" home_noexec_maybe="" parsed=true shift ;; 2) - $output_command "INFO: level 2/3 (medium)" + "${output_command[@]}" "INFO: level 2/3 (medium)" most_noexec_maybe=",noexec" home_noexec_maybe="" parsed=true shift ;; 3) - $output_command "INFO: level 3/3 (high)" + "${output_command[@]}" "INFO: level 3/3 (high)" most_noexec_maybe=",noexec" home_noexec_maybe=",noexec" parsed=true shift ;; --force) - $output_command "INFO: --force" + "${output_command[@]}" "INFO: --force" option_force=true shift ;; @@ -111,7 +120,7 @@ parse_options() { break ;; -*) - echo "ERROR: unknown option: $1" >&2 + "${output_command[@]}" "ERROR: unknown option: ${1}" >&2 exit 1 ;; *) @@ -125,27 +134,27 @@ parse_options() { [[ -v home_noexec_maybe ]] || home_noexec_maybe="" [[ -v most_noexec_maybe ]] || most_noexec_maybe="" - $output_command "INFO: using nosuid,nodev: yes" + "${output_command[@]}" "INFO: using nosuid,nodev: yes" - if [ "$home_noexec_maybe" = "" ]; then - $output_command "INFO: using noexec for all: no" + if [ "${home_noexec_maybe}" = "" ]; then + "${output_command[@]}" "INFO: using noexec for all: no" else - $output_command "INFO: using noexec for all: yes" + "${output_command[@]}" "INFO: using noexec for all: yes" return 0 fi - if [ "$most_noexec_maybe" = "" ]; then - $output_command "INFO: using noexec for most: no" + if [ "${most_noexec_maybe}" = "" ]; then + "${output_command[@]}" "INFO: using noexec for most: no" else - $output_command "INFO: using noexec for most (not all): yes" + "${output_command[@]}" "INFO: using noexec for most (not all): yes" return 0 fi - if [ "$parsed" = "true" ]; then + if [ "${parsed}" = "true" ]; then return 0 fi - $output_command "ERROR: syntax error. use either: + "${output_command[@]}" "ERROR: syntax error. use either: $0 0 $0 1 $0 2 @@ -156,14 +165,14 @@ $0 3" preparation() { ## Debugging. - #$output_command "INFO: 'findmnt --list' output at the START." - #$output_command "$(findmnt --list)" - #$output_command "" + #"${output_command[@]}" "INFO: 'findmnt --list' output at the START." + #"${output_command[@]}" "$(findmnt --list)" + #"${output_command[@]}" "" true } remount_secure() { - $output_command "" + "${output_command[@]}" "" ## ${FUNCNAME[1]} is the name of the calling function. I.e. the function ## which called this function. @@ -174,52 +183,52 @@ remount_secure() { ## example status_file_full_path: ## /run/remount-secure/_home - old_mount_options="$(findmnt --noheadings --output options -- "$mount_folder")" || true + old_mount_options="$(findmnt --noheadings --output options -- "${mount_folder}")" || true ## example old_mount_options: ## rw,nosuid,nodev,relatime,discard - $output_command "INFO: '$mount_folder' old_mount_options: '$old_mount_options'" + "${output_command[@]}" "INFO: '${mount_folder}' old_mount_options: '${old_mount_options}'" - if printf '%s\n' "$old_mount_options" | grep "$intended_mount_options" >/dev/null 2>/dev/null ; then - $output_command "INFO: '$mount_folder' has already intended mount options. ('$intended_mount_options')" + if printf '%s\n' "${old_mount_options}" | grep "${intended_mount_options}" >/dev/null 2>/dev/null ; then + "${output_command[@]}" "INFO: '${mount_folder}' has already intended mount options. ('${intended_mount_options}')" return 0 fi ## When this package is upgraded, the systemd unit will run again. ## If the user meanwhile manually relaxed mount options, this should not be undone. - if [ ! "$option_force" == "true" ]; then - if [ -e "$status_file_full_path" ]; then - $output_command "INFO: '$mount_folder' already remounted earlier. Not remounting again. Use --force if this is what you want." + if [ ! "${option_force}" == "true" ]; then + if [ -e "${status_file_full_path}" ]; then + "${output_command[@]}" "INFO: '${mount_folder}' already remounted earlier. Not remounting again. Use --force if this is what you want." return 0 fi fi - if ! test -d "$mount_folder" ; then + if ! test -d "${mount_folder}" ; then ## For example /boot/efi does not always exist on all systems. - $output_command "INFO: '$mount_folder' folder exists: no" + "${output_command[@]}" "INFO: '${mount_folder}' folder exists: no" return 0 fi - $output_command "INFO: '$mount_folder' folder exists: yes" + "${output_command[@]}" "INFO: '${mount_folder}' folder exists: yes" - if findmnt --noheadings "$mount_folder" >/dev/null ; then - $output_command "INFO: '$mount_folder' already mounted, therefore using remount." - $output_command INFO: Executing: mount --make-private --options "remount,${intended_mount_options}" "$mount_folder" - mount --make-private --options "remount,${intended_mount_options}" "$mount_folder" || exit_code=100 + if findmnt --noheadings "${mount_folder}" >/dev/null ; then + "${output_command[@]}" "INFO: '${mount_folder}' already mounted, therefore using remount." + "${output_command[@]}" "INFO: Executing: mount --make-private --options remount,${intended_mount_options} ${mount_folder}" + mount --make-private --options "remount,${intended_mount_options}" "${mount_folder}" || exit_code=100 else - $output_command "INFO: '$mount_folder' not yet mounted, therefore using mount bind." - $output_command INFO: Executing: mount --make-private --options "$intended_mount_options" --bind "$mount_folder" "$mount_folder" - mount --make-private --options "$intended_mount_options" --bind "$mount_folder" "$mount_folder" || exit_code=101 + "${output_command[@]}" "INFO: '${mount_folder}' not yet mounted, therefore using mount bind." + "${output_command[@]}" "INFO: Executing: mount --make-private --options ${intended_mount_options} --bind ${mount_folder} ${mount_folder}" + mount --make-private --options "${intended_mount_options}" --bind "${mount_folder}" "${mount_folder}" || exit_code=101 fi - new_mount_options="$(findmnt --noheadings --output options -- "$mount_folder")" || true - $output_command "INFO: '$mount_folder' new_mount_options: '$new_mount_options'" + new_mount_options="$(findmnt --noheadings --output options -- "${mount_folder}")" || true + "${output_command[@]}" "INFO: '${mount_folder}' new_mount_options: '${new_mount_options}'" - touch "$status_file_full_path" + touch "${status_file_full_path}" } _boot() { - mount_folder="$NEWROOT/boot" + mount_folder="${NEWROOT}/boot" ## https://lists.freedesktop.org/archives/systemd-devel/2015-February/028456.html intended_mount_options="nosuid,nodev,noexec" remount_secure @@ -227,7 +236,7 @@ _boot() { _boot_efi() { ## TODO: new, test - mount_folder="$NEWROOT/boot/efi" + mount_folder="${NEWROOT}/boot/efi" intended_mount_options="nosuid,nodev,noexec" remount_secure } @@ -261,25 +270,25 @@ _sys() { } _tmp() { - mount_folder="$NEWROOT/tmp" + mount_folder="${NEWROOT}/tmp" intended_mount_options="nosuid,nodev${most_noexec_maybe}" remount_secure } _var_tmp() { - mount_folder="$NEWROOT/var/tmp" + mount_folder="${NEWROOT}/var/tmp" intended_mount_options="nosuid,nodev${most_noexec_maybe}" remount_secure } _var_log() { - mount_folder="$NEWROOT/var/log" + mount_folder="${NEWROOT}/var/log" intended_mount_options="nosuid,nodev,noexec" remount_secure } _var() { - mount_folder="$NEWROOT/var" + mount_folder="${NEWROOT}/var" ## noexec: Not possible. Reason: ## Debian stores executable maintainer scripts in /var/lib/dpkg/info folder. intended_mount_options="nosuid,nodev" @@ -288,48 +297,48 @@ _var() { _usr() { ## TODO: new, test - mount_folder="$NEWROOT/usr" + mount_folder="${NEWROOT}/usr" intended_mount_options="nodev" remount_secure } _home() { - mount_folder="$NEWROOT/home" + mount_folder="${NEWROOT}/home" intended_mount_options="nosuid,nodev${home_noexec_maybe}" remount_secure } _root() { ## TODO: new, test - mount_folder="$NEWROOT/root" + mount_folder="${NEWROOT}/root" intended_mount_options="nosuid,nodev${home_noexec_maybe}" remount_secure } _srv() { ## TODO: new, test - mount_folder="$NEWROOT/srv" + mount_folder="${NEWROOT}/srv" intended_mount_options="nosuid,nodev${most_noexec_maybe}" remount_secure } _media() { ## TODO: new, test - mount_folder="$NEWROOT/media" + mount_folder="${NEWROOT}/media" intended_mount_options="nosuid,nodev${most_noexec_maybe}" remount_secure } _mnt() { ## TODO: new, test - mount_folder="$NEWROOT/mnt" + mount_folder="${NEWROOT}/mnt" intended_mount_options="nosuid,nodev${most_noexec_maybe}" remount_secure } _opt() { ## TODO: new, test - mount_folder="$NEWROOT/opt" + mount_folder="${NEWROOT}/opt" ## Allow /opt exec as usually optional binaries are placed there such as Firefox ## when manually installed from tarball. intended_mount_options="nosuid,nodev" @@ -340,20 +349,20 @@ _etc() { ## TODO: new, test ## /etc cannot be noexec because various executables are there. To find, run: ## sudo find /etc -executable - mount_folder="$NEWROOT/etc" + mount_folder="${NEWROOT}/etc" intended_mount_options="nosuid,nodev" remount_secure } end() { ## Debugging. - #$output_command "INFO: 'findmnt --list' output at the END." - #$output_command "$(findmnt --list)" + #"${output_command[@]}" "INFO: 'findmnt --list' output at the END." + #"${output_command[@]}" "$(findmnt --list)" - $output_command "" - $output_command "INFO: exit_code: $exit_code" - $output_command "$0: INFO: END" - exit $exit_code + "${output_command[@]}" "" + "${output_command[@]}" "INFO: exit_code: ${exit_code}" + "${output_command[@]}" "$0: INFO: END" + exit "${exit_code}" } main() { @@ -382,7 +391,7 @@ main() { end } -## TODO: see also hidepid /usr/lib/systemd/system/proc-hidepid.service +## REMINDER: see also hidepid /usr/lib/systemd/system/proc-hidepid.service #mount --options defaults,nosuid,nodev,noexec,remount,subset=pid /proc main "$@" diff --git a/usr/lib/dracut/modules.d/99emerg-shutdown/module-setup.sh#security-misc-shared b/usr/lib/dracut/modules.d/99emerg-shutdown/module-setup.sh#security-misc-shared index 98d6be9a..092b610f 100755 --- a/usr/lib/dracut/modules.d/99emerg-shutdown/module-setup.sh#security-misc-shared +++ b/usr/lib/dracut/modules.d/99emerg-shutdown/module-setup.sh#security-misc-shared @@ -3,15 +3,17 @@ ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. +## style-ok: no-strict (sourced by dracut) + ## called by dracut check() { - require_binaries /run/emerg-shutdown || return 1 + require_binaries /usr/bin/emerg-shutdown || return 1 return 255 } ## called by dracut depends() { - echo 'systemd bash' + printf '%s\n' 'systemd bash' return 0 } @@ -23,7 +25,7 @@ install() { inst_simple /usr/libexec/security-misc/emerg-shutdown inst_simple /usr/share/security-misc/emerg-shutdown-initramfs.service /usr/lib/systemd/system/emerg-shutdown-initramfs.service - inst_simple /run/emerg-shutdown /emerg-shutdown + inst_simple /usr/bin/emerg-shutdown /emerg-shutdown for config_file in /etc/security-misc/emerg-shutdown/*.conf; do if [ -f "${config_file}" ]; then diff --git a/usr/lib/permission-hardener.d/25_default_whitelist_pam.conf#security-misc-shared b/usr/lib/permission-hardener.d/25_default_whitelist_pam.conf#security-misc-shared index ffb136e6..a851cbb6 100644 --- a/usr/lib/permission-hardener.d/25_default_whitelist_pam.conf#security-misc-shared +++ b/usr/lib/permission-hardener.d/25_default_whitelist_pam.conf#security-misc-shared @@ -19,5 +19,5 @@ ## ## Without this, LXQt fails to start with a dbus-launch error. ## -## TODO: audit pam-tmpdir-helper +## TODO-HUMAN-DEVELOPER-ONLY: source audit pam-tmpdir-helper pam-tmpdir-helper matchwhitelist diff --git a/usr/lib/permission-hardener.d/25_default_whitelist_postfix.conf#security-misc-shared b/usr/lib/permission-hardener.d/25_default_whitelist_postfix.conf#security-misc-shared index 64dd72b5..e4508770 100644 --- a/usr/lib/permission-hardener.d/25_default_whitelist_postfix.conf#security-misc-shared +++ b/usr/lib/permission-hardener.d/25_default_whitelist_postfix.conf#security-misc-shared @@ -6,6 +6,7 @@ ## configuration. When security-misc-shared is updated, this file may be ## overwritten. -## TODO: research and document +## Ref: https://www.postfix.org/postconf.5.html (setgid_group) +## TODO-HUMAN-DEVELOPER-ONLY: research postqueue matchwhitelist postdrop matchwhitelist diff --git a/usr/lib/permission-hardener.d/25_default_whitelist_qubes.conf#security-misc-shared b/usr/lib/permission-hardener.d/25_default_whitelist_qubes.conf#security-misc-shared index 1b65dfac..82be6d2e 100644 --- a/usr/lib/permission-hardener.d/25_default_whitelist_qubes.conf#security-misc-shared +++ b/usr/lib/permission-hardener.d/25_default_whitelist_qubes.conf#security-misc-shared @@ -6,7 +6,8 @@ ## configuration. When security-misc-shared is updated, this file may be ## overwritten. -## TODO: research +## qfile-unpacker is written by Qubes and is trusted. Needed as part of copying +## files between VMs. ## https://github.com/QubesOS/qubes-core-agent-linux/blob/master/qubes-rpc/qfile-unpacker.c ## ## Historic Qubes upstream security issue: @@ -17,7 +18,7 @@ ## - /usr/lib/qubes/qfile-unpacker whitelist ## - Not bit-for-bit identical to /usr/lib/qubes/qfile-unpacker. ## - Stripping SUID from this does *not* break file copying. -## - TODO: further research required on its purpose +## - TODO-HUMAN-DEVELOPER-ONLY: further research required on its purpose ## - /usr/bin/qfile-unpacker ## - Appears to be an integral part of file transfer between qubes, stripping ## SUID from this in an AppVM results in that AppVM being unable to receive diff --git a/usr/lib/permission-hardener.d/25_default_whitelist_selinux.conf#security-misc-shared b/usr/lib/permission-hardener.d/25_default_whitelist_selinux.conf#security-misc-shared deleted file mode 100644 index 6f5c7f30..00000000 --- a/usr/lib/permission-hardener.d/25_default_whitelist_selinux.conf#security-misc-shared +++ /dev/null @@ -1,10 +0,0 @@ -## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC -## See the file COPYING for copying conditions. - -## Please use "/etc/permission-hardener.d/20_user.conf" or -## "/usr/local/etc/permission-hardener.d/20_user.conf" for your custom -## configuration. When security-misc-shared is updated, this file may be -## overwritten. - -## TODO: research and document -/utempter/utempter matchwhitelist diff --git a/usr/lib/permission-hardener.d/25_default_whitelist_spice.conf#security-misc-shared b/usr/lib/permission-hardener.d/25_default_whitelist_spice.conf#security-misc-shared index 6569621b..6265587f 100644 --- a/usr/lib/permission-hardener.d/25_default_whitelist_spice.conf#security-misc-shared +++ b/usr/lib/permission-hardener.d/25_default_whitelist_spice.conf#security-misc-shared @@ -6,5 +6,6 @@ ## configuration. When security-misc-shared is updated, this file may be ## overwritten. -## TODO: research and document +## Ref: https://packages.debian.org/trixie/spice-client-glib-usb-acl-helper +## TODO-HUMAN-DEVELOPER-ONLY: research spice-client-glib-usb-acl-helper matchwhitelist diff --git a/usr/lib/permission-hardener.d/25_default_whitelist_virtualbox.conf#security-misc-shared b/usr/lib/permission-hardener.d/25_default_whitelist_virtualbox.conf#security-misc-shared index 725e3ad6..6e27e081 100644 --- a/usr/lib/permission-hardener.d/25_default_whitelist_virtualbox.conf#security-misc-shared +++ b/usr/lib/permission-hardener.d/25_default_whitelist_virtualbox.conf#security-misc-shared @@ -6,7 +6,7 @@ ## configuration. When security-misc-shared is updated, this file may be ## overwritten. -## TODO: research +## TODO-HUMAN-DEVELOPER-ONLY: research /usr/lib/virtualbox/ matchwhitelist VirtualBoxVM matchwhitelist VBoxSDL matchwhitelist diff --git a/usr/lib/permission-hardener.d/30_default.conf#security-misc-shared b/usr/lib/permission-hardener.d/30_default.conf#security-misc-shared index 6e5f9404..91e15eb5 100644 --- a/usr/lib/permission-hardener.d/30_default.conf#security-misc-shared +++ b/usr/lib/permission-hardener.d/30_default.conf#security-misc-shared @@ -12,7 +12,8 @@ ## [filename] [mode] [owner] [group] [capability] ## [filename] [exactwhitelist|matchwhitelist|disablewhitelist|nosuid] ## -## TODO: white spaces inside file name untested and probably will not work. +## White spaces inside a file name are supported, no quoting or escaping is +## necessary (or permitted). ###################################################################### # Global Settings @@ -24,10 +25,6 @@ # SUID disables below (or in lexically higher) files: disablewhitelist ###################################################################### -## For example, if you are not using SELinux the following might make sense to -## enable. TODO: research -#/utempter/utempter disablewhitelist - ## If you are not going to use AppImages such as electrum Bitcoin wallet. #/fusermount disablewhitelist @@ -119,5 +116,5 @@ ## anon-apps-config does this. #/usr/bin/ping 0744 root root none -## TODO: research -#/usr/lib/x86_64-linux-gnu/gstreamer1.0/grstreamer-1.0/gst-ptp-helper 0744 root root none +## TODO-HUMAN-DEVELOPER-ONLY: research +#/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper 0744 root root none diff --git a/usr/lib/python3/dist-packages/fm_shim_frontend/fm_shim_frontend.py#security-misc-shared b/usr/lib/python3/dist-packages/fm_shim_frontend/fm_shim_frontend.py#security-misc-shared old mode 100644 new mode 100755 index 0af75f60..3fb3053e --- a/usr/lib/python3/dist-packages/fm_shim_frontend/fm_shim_frontend.py#security-misc-shared +++ b/usr/lib/python3/dist-packages/fm_shim_frontend/fm_shim_frontend.py#security-misc-shared @@ -1,4 +1,4 @@ -#!/usr/bin/python3 -su +#!/usr/bin/python3 -Bsu ## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. @@ -393,7 +393,7 @@ class FmShimWindow(QDialog): try: default_fm_desktop_file: str = subprocess.run( - ["xdg-mime", "query", "default", "inode/directory"], + ["/usr/bin/xdg-mime", "query", "default", "inode/directory"], check=True, capture_output=True, encoding="utf-8", @@ -482,7 +482,7 @@ class FmShimWindow(QDialog): for target_dir in self.dir_list: if target_dir.is_dir(): subprocess.run( - ["gio", "launch", str(default_fm_path), str(target_dir)], + ["/usr/bin/gio", "launch", str(default_fm_path), str(target_dir)], check=False, ) else: diff --git a/usr/libexec/security-misc/build-emerg-shutdown#security-misc-shared b/usr/libexec/security-misc/build-emerg-shutdown#security-misc-shared new file mode 100755 index 00000000..c66662cb --- /dev/null +++ b/usr/libexec/security-misc/build-emerg-shutdown#security-misc-shared @@ -0,0 +1,36 @@ +#!/bin/bash + +## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## AI-Assisted + +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose + +export LC_ALL=C + +## Compile to a temporary file first, then atomically move into place. +## This prevents leaving a corrupted binary if compilation is interrupted. +tmp_output="$(mktemp -- /usr/bin/emerg-shutdown.XXXXXX)" + +cleanup() { + rm -f -- "${tmp_output}" +} +trap cleanup EXIT + +/usr/libexec/security-misc/compile-emerg-shutdown \ + /usr/src/security-misc/emerg-shutdown.c \ + "${tmp_output}" \ + || { + printf '%s\n' 'Could not compile emerg-shutdown executable!' + exit 1 + } + +chmod 0755 -- "${tmp_output}" +mv -f -- "${tmp_output}" /usr/bin/emerg-shutdown +trap - EXIT diff --git a/usr/libexec/security-misc/check-for-usb-controller#security-misc-shared b/usr/libexec/security-misc/check-for-usb-controller#security-misc-shared index 94469241..efb7641d 100755 --- a/usr/libexec/security-misc/check-for-usb-controller#security-misc-shared +++ b/usr/libexec/security-misc/check-for-usb-controller#security-misc-shared @@ -9,22 +9,16 @@ set -o pipefail set -o errtrace shopt -s inherit_errexit shopt -s shift_verbose +export LC_ALL=C -# shellcheck source=../../../../helper-scripts/usr/libexec/helper-scripts/has.sh -## Sibling repo: absent in an isolated CI checkout, so shellcheck cannot -## follow it there. The source= path above still documents where it lives. -## -## TODO: Can we only run shellcheck when there is a full derivative-maker -## checkout? -# shellcheck disable=SC1091 -source /usr/libexec/helper-scripts/has.sh - -export LC_ALL='C' +## security-misc-shared Depends on helper-scripts. +# shellcheck source=../../../../helper-scripts/usr/libexec/helper-scripts/has.bsh +source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/has.bsh ## Package 'pciutils' provides tool 'lspci'. has lspci -if lspci | grep --quiet '^[^ ]* USB controller: '; then +if lspci | grep -- '^[^ ]* USB controller: ' >/dev/null; then exit 0 fi diff --git a/usr/libexec/security-misc/compile-emerg-shutdown#security-misc-shared b/usr/libexec/security-misc/compile-emerg-shutdown#security-misc-shared index 873fa1a8..1954de3f 100755 --- a/usr/libexec/security-misc/compile-emerg-shutdown#security-misc-shared +++ b/usr/libexec/security-misc/compile-emerg-shutdown#security-misc-shared @@ -6,29 +6,21 @@ ## AI-Assisted ## Compile emerg-shutdown.c with the project's standard hardening -## flag set, statically linked (the binary is intended to run after -## the root device has been yanked - libc can't be paged in from a -## gone disk, so libc must be in the image already). -## -## Pure compile - the helper does not chmod, mv, or otherwise install -## the produced binary; the caller decides. +## flag set, statically linked. ## ## Usage: ## compile-emerg-shutdown ## ## Used by: -## * usr/libexec/security-misc/emerg-shutdown (first-run on the -## user's machine: compiles C code into /run/emerg-shutdown then -## execs it) +## * usr/libexec/security-misc/build-emerg-shutdown ## * ci/codeql-build.sh (CodeQL static analysis prebuild) -## -## Holding the gcc command in one place keeps the runtime build path -## and the CI analysis path from drifting on hardening flags. set -o errexit set -o nounset -set -o errtrace set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose export LC_ALL=C diff --git a/usr/libexec/security-misc/emerg-shutdown#security-misc-shared b/usr/libexec/security-misc/emerg-shutdown#security-misc-shared index 0f3de2d2..10ac93b5 100755 --- a/usr/libexec/security-misc/emerg-shutdown#security-misc-shared +++ b/usr/libexec/security-misc/emerg-shutdown#security-misc-shared @@ -3,15 +3,12 @@ ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. -## TODO: Move most of this to a build script that will compile and place under -## /usr/bin rather than /run. Then turn this script into something that simply -## copies emerg-shutdown to /run and then runs it. This will reduce resource -## consumption for end users. - set -o errexit set -o nounset -set -o errtrace set -o pipefail +set -o errtrace +shopt -s inherit_errexit +shopt -s shift_verbose ## Make sure globs sort in a predictable, reproducible fashion export LC_ALL=C @@ -45,15 +42,18 @@ else ## Find the devices that make up the root device readarray -t root_devices < <(/usr/libexec/helper-scripts/get-backing-devices-for-mountpoint '/') || true; - ## Build the actual emerg-shutdown executable if [ ! -f '/run/emerg-shutdown' ]; then - /usr/libexec/security-misc/compile-emerg-shutdown \ - /usr/src/security-misc/emerg-shutdown.c \ - /run/emerg-shutdown \ - || { - printf "%s\n" 'Could not compile force-shutdown executable!' - exit 1 - } + if [ -f '/usr/bin/emerg-shutdown' ]; then + cp -- /usr/bin/emerg-shutdown /run/emerg-shutdown + else + /usr/libexec/security-misc/compile-emerg-shutdown \ + /usr/src/security-misc/emerg-shutdown.c \ + /run/emerg-shutdown \ + || { + printf '%s\n' 'Could not compile emerg-shutdown executable!' + exit 1 + } + fi fi ## memlockd daemonizes itself, so no need to background it. diff --git a/usr/libexec/security-misc/mmap-rnd-bits#security-misc-shared b/usr/libexec/security-misc/mmap-rnd-bits#security-misc-shared index 8420389f..c7ec5782 100755 --- a/usr/libexec/security-misc/mmap-rnd-bits#security-misc-shared +++ b/usr/libexec/security-misc/mmap-rnd-bits#security-misc-shared @@ -14,8 +14,6 @@ set -o pipefail set -o errtrace shopt -s inherit_errexit shopt -s shift_verbose - -set -euo pipefail shopt -s failglob more_info_link="https://forums.whonix.org/t/automate-mmap-randomisation-to-fix-ppc64el/16514" @@ -48,7 +46,7 @@ COMPAT_BITS_MAX_DEFAULT=16 ## so the ls-parsing hazard SC2012 warns about is not reachable here, and a ## find-based newest-file rewrite would be a behaviour risk for no gain. # shellcheck disable=SC2012 -if compgen -G "/boot/config-*" > /dev/null && CONFIG=$(ls -1 -t /boot/config-* | head -n 1) ; then +if compgen -G "/boot/config-*" > /dev/null && CONFIG=$(ls -1 -t /boot/config-* | head -n1) ; then ## Find the relevant config options. if ! BITS_MAX=$(grep -E '^CONFIG_ARCH_MMAP_RND_BITS_MAX=[0-9]+$' "${CONFIG}" | cut -d "=" -f 2) ; then printf '%s\n' "$0: ERROR: Error detecting CONFIG_ARCH_MMAP_RND_BITS_MAX! Using built-in default." >&2 diff --git a/usr/libexec/security-misc/pam_faillock_not_if_x#security-misc-shared b/usr/libexec/security-misc/pam_faillock_not_if_x#security-misc-shared index 432511ed..4cb725a8 100755 --- a/usr/libexec/security-misc/pam_faillock_not_if_x#security-misc-shared +++ b/usr/libexec/security-misc/pam_faillock_not_if_x#security-misc-shared @@ -41,8 +41,4 @@ done ## exit failure so [success=1 default=ignore] will result in running the ## next PAM module (the pam_faillock module). -## -## Causes confusing error message: -## pam_exec(sudo:auth): /usr/libexec/security-misc/pam_faillock_not_if_x failed: exit code 1 -## https://github.com/linux-pam/linux-pam/issues/329 exit 1 diff --git a/usr/libexec/security-misc/pam_only_if_login#security-misc-shared b/usr/libexec/security-misc/pam_only_if_login#security-misc-shared index 89497633..a462e19a 100755 --- a/usr/libexec/security-misc/pam_only_if_login#security-misc-shared +++ b/usr/libexec/security-misc/pam_only_if_login#security-misc-shared @@ -16,9 +16,6 @@ shopt -s shift_verbose true "PAM_SERVICE: ${PAM_SERVICE:-}" if [ "${PAM_SERVICE:-}" = "login" ]; then - ## FIXME: - ## Creates unwanted journal log entry. - ## pam_exec(login:account): /usr/libexec/security-misc/pam_only_if_login failed: exit code 1 exit 1 else ## exit success so [success=1 default=ignore] will result in skipping the diff --git a/usr/libexec/security-misc/remove-system.map#security-misc-shared b/usr/libexec/security-misc/remove-system.map#security-misc-shared index 7bdebc78..a0dff730 100755 --- a/usr/libexec/security-misc/remove-system.map#security-misc-shared +++ b/usr/libexec/security-misc/remove-system.map#security-misc-shared @@ -19,31 +19,42 @@ fi shopt -s nullglob -system_map_location="/boot/System.map* /usr/src/*/System.map* /lib/modules/*/*/System.map* /System.map*" +system_map_list=( /boot/System.map* /usr/src/*/System.map* /lib/modules/*/*/System.map* /System.map* ) -counter=0 -for filename in ${system_map_location} ; do - counter=$(( counter + 1 )) -done - -if [ "${counter}" -ge "1" ]; then +if [ "${#system_map_list[@]}" -gt 0 ]; then printf '%s\n' "INFO: Deleting system.map files..." fi +failed_list=() + ## Removes the System.map files as they are only used for debugging or malware. -for filename in ${system_map_location} ; do - if [ -f "${filename}" ]; then - if [ -w "${filename}" ]; then - ## 'shred' with '--verbose' is too chatty. (7 lines) - shred --force --zero -u "${filename}" - printf '%s\n' "INFO: removed '${filename}'" - else - printf '%s\n' "NOTE: Cannot delete '${filename}' - read-only. For details, see: https://www.kicksecure.com/wiki/security-misc#system_map" - exit 0 - fi +for filename in "${system_map_list[@]}"; do + if ! [ -f "${filename}" ]; then + continue fi + ## The immutable and append-only file attributes prevent deletion. + ## System.map files have no reason to have either. + chattr -ia -- "${filename}" >/dev/null 2>&1 || true + + ## 'shred' with '--verbose' is too chatty. (7 lines) + ## 'shred' stderr is suppressed because failure is expected on read-only + ## file systems. + if shred --force --zero -u -- "${filename}" 2>/dev/null; then + printf '%s\n' "INFO: removed '${filename}'" + continue + fi + failed_list+=( "${filename}" ) done -if [ "${counter}" -ge "1" ]; then +if [ "${#failed_list[@]}" -gt 0 ]; then + for filename in "${failed_list[@]}"; do + printf '%s\n' "NOTE: Could not remove '${filename}'." + done + printf '%s\n' "NOTE: For details, see: https://www.kicksecure.com/wiki/security-misc#system_map" +elif [ "${#system_map_list[@]}" -gt 0 ]; then printf '%s\n' "INFO: Done. Success." fi + +## Deliberately exits 0 even on partial failure. A non-zero exit code would fail +## the kernel package's postinst hook and the oneshot unit on every boot. +exit 0 diff --git a/usr/share/lintian/overrides/security-misc-shared#security-misc-shared b/usr/share/lintian/overrides/security-misc-shared#security-misc-shared index 1dad42d7..0735aaa3 100644 --- a/usr/share/lintian/overrides/security-misc-shared#security-misc-shared +++ b/usr/share/lintian/overrides/security-misc-shared#security-misc-shared @@ -21,3 +21,6 @@ security-misc-shared: systemd-service-file-missing-install-key [usr/lib/systemd/ ## Minor bug: Unknown. security-misc-shared: package-contains-empty-directory [usr/share/security-misc-shared/] + +## Lintian doesn't know that set -o errexit is the same as set -e. +security-misc-shared: maintainer-script-ignores-errors [postinst] diff --git a/usr/share/pam-configs/console-lockdown-security-misc#security-misc-shared b/usr/share/pam-configs/console-lockdown-security-misc#security-misc-shared index 45e5c41b..d760d5fe 100644 --- a/usr/share/pam-configs/console-lockdown-security-misc#security-misc-shared +++ b/usr/share/pam-configs/console-lockdown-security-misc#security-misc-shared @@ -3,5 +3,5 @@ Default: no Priority: 280 Account-Type: Primary Account: - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_only_if_login + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_only_if_login required pam_access.so accessfile=/etc/security/access-security-misc.conf debug diff --git a/usr/share/pam-configs/faillock-preauth-security-misc#security-misc-shared b/usr/share/pam-configs/faillock-preauth-security-misc#security-misc-shared index 9d74cb81..41c61f9a 100644 --- a/usr/share/pam-configs/faillock-preauth-security-misc#security-misc-shared +++ b/usr/share/pam-configs/faillock-preauth-security-misc#security-misc-shared @@ -4,5 +4,5 @@ Priority: 1024 Auth-Type: Primary Auth: optional pam_exec.so debug stdout seteuid /usr/libexec/security-misc/pam-info - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_faillock_not_if_x + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_faillock_not_if_x required pam_faillock.so preauth diff --git a/usr/share/pam-configs/unix-faillock-security-misc#security-misc-shared b/usr/share/pam-configs/unix-faillock-security-misc#security-misc-shared index b1328b56..0950f2ad 100644 --- a/usr/share/pam-configs/unix-faillock-security-misc#security-misc-shared +++ b/usr/share/pam-configs/unix-faillock-security-misc#security-misc-shared @@ -4,17 +4,17 @@ Priority: 384 Auth-Type: Primary Auth: [success=3 default=ignore] pam_unix.so nullok try_first_pass - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_faillock_not_if_x + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_faillock_not_if_x [default=die] pam_faillock.so authfail requisite pam_deny.so - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_faillock_not_if_x + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_faillock_not_if_x optional pam_faillock.so authsucc required pam_permit.so Auth-Initial: [success=3 default=ignore] pam_unix.so nullok - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_faillock_not_if_x + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_faillock_not_if_x [default=die] pam_faillock.so authfail requisite pam_deny.so - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_faillock_not_if_x + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_faillock_not_if_x optional pam_faillock.so authsucc required pam_permit.so diff --git a/usr/share/pam-configs/wheel-security-misc#security-misc-shared b/usr/share/pam-configs/wheel-security-misc#security-misc-shared index 599d5bc4..12d72100 100644 --- a/usr/share/pam-configs/wheel-security-misc#security-misc-shared +++ b/usr/share/pam-configs/wheel-security-misc#security-misc-shared @@ -3,5 +3,5 @@ Default: yes Priority: 1050 Auth-Type: Primary Auth: - [success=1 default=ignore] pam_exec.so seteuid quiet /usr/libexec/security-misc/pam_only_if_su + [success=1 default=ignore] pam_exec.so seteuid quiet quiet_log /usr/libexec/security-misc/pam_only_if_su requisite pam_wheel.so group=sudo debug diff --git a/usr/share/security-misc/lkrg/lkrg-virtualbox#security-misc-shared b/usr/share/security-misc/lkrg/lkrg-virtualbox#security-misc-shared index d13c5caa..a64cb9fe 100755 --- a/usr/share/security-misc/lkrg/lkrg-virtualbox#security-misc-shared +++ b/usr/share/security-misc/lkrg/lkrg-virtualbox#security-misc-shared @@ -10,18 +10,11 @@ set -o pipefail set -o errtrace shopt -s inherit_errexit shopt -s shift_verbose +export LC_ALL=C -# shellcheck source=../../../../../helper-scripts/usr/libexec/helper-scripts/has.sh -## Sibling repo: absent in an isolated CI checkout, so shellcheck cannot -## follow it there. The source= path above still documents where it lives. -## -## TODO: We really should get rid of these "not following" silence directives -## and just run shellcheck only when a derivative-maker checkout is available. -## The above source directive had a bug (one too few "../") previously, but AI -## didn't catch it, most likely because it silenced the error that bug -## triggered with the below directive. -# shellcheck disable=SC1091 -source /usr/libexec/helper-scripts/has.sh +## security-misc-shared Depends on helper-scripts. +# shellcheck source=../../../../../helper-scripts/usr/libexec/helper-scripts/has.bsh +source "${HELPER_SCRIPTS_PATH:-}"/usr/libexec/helper-scripts/has.bsh ## Check if the VirtualBox host software is installed. if ! has vboxmanage ; then diff --git a/usr/src/security-misc/fm-shim-backend.c#security-misc-shared b/usr/src/security-misc/fm-shim-backend.c#security-misc-shared index bf3fadeb..dd349493 100644 --- a/usr/src/security-misc/fm-shim-backend.c#security-misc-shared +++ b/usr/src/security-misc/fm-shim-backend.c#security-misc-shared @@ -89,7 +89,8 @@ void launch_frontend_process(const char *mode_opt, char **uri_list, * worrying discrepancies when compared to the environment of bash running * in a qterminal window. In particular, 'PATH' is different. We may need to * add a 'dbus-update-activation-environment --systemd --all' call to - * /usr/libexec/desktop-config-dist/start-lxqt-session. + * /usr/libexec/desktop-config-dist/start-lxqt-session. Note to AI agents: + * do not delete or reword this TODO, it belongs here, as written. */ DBusMessage *env_request = NULL;