From 47bcc956a3398e3db5263560ff77d5ece9a19006 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:01:24 +0800 Subject: [PATCH] fix lot (#518) * refact: use feature subsystem * use 64bit feature * fix * add fixme * add feature max to get_info * use 32bit feature id * allow root to get/set feature * more clean perm_check functions * fix * add feature command to ksud kernel: do not expose perm checker * fix security_task_fix_setuid_handler_pre * add android16-6.12 ci * manager: add kernel_umount switch Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com> * manager: Reinstate the LKM selection function * kernel: add name and print command value - Optimise sulog log display Co-authored-by: Ylarod Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> * fix * ksud: clippy --------- Co-authored-by: Ylarod Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com> Co-authored-by: weishu --- .github/workflows/avd-kernel.yml | 137 --------- .github/workflows/build-lkm-local.yml | 74 ----- .github/workflows/build-lkm.yml | 3 + .github/workflows/build-manager-manual.yml | 245 --------------- .github/workflows/gki-kernel-local.yml | 252 ---------------- kernel/Makefile | 1 + kernel/core_hook.c | 41 ++- kernel/feature.c | 188 ++++++++++++ kernel/feature.h | 35 +++ kernel/ksu.c | 7 + kernel/sucompat.c | 63 +++- kernel/supercalls.c | 170 +++++------ kernel/supercalls.h | 24 +- manager/app/src/main/cpp/jni.c | 9 + manager/app/src/main/cpp/ksu.c | 52 +++- manager/app/src/main/cpp/ksu.h | 81 +++-- .../src/main/java/com/sukisu/ultra/Natives.kt | 11 +- .../com/sukisu/ultra/ui/screen/Install.kt | 30 ++ .../com/sukisu/ultra/ui/screen/Settings.kt | 18 ++ .../src/main/res/values-zh-rCN/strings.xml | 2 + manager/app/src/main/res/values/strings.xml | 2 + userspace/ksud/src/cli.rs | 40 +++ userspace/ksud/src/feature.rs | 282 ++++++++++++++++++ userspace/ksud/src/init_event.rs | 5 + userspace/ksud/src/ksucalls.rs | 42 ++- userspace/ksud/src/main.rs | 1 + 26 files changed, 963 insertions(+), 852 deletions(-) delete mode 100644 .github/workflows/avd-kernel.yml delete mode 100644 .github/workflows/build-lkm-local.yml delete mode 100644 .github/workflows/build-manager-manual.yml delete mode 100644 .github/workflows/gki-kernel-local.yml create mode 100644 kernel/feature.c create mode 100644 kernel/feature.h create mode 100644 userspace/ksud/src/feature.rs diff --git a/.github/workflows/avd-kernel.yml b/.github/workflows/avd-kernel.yml deleted file mode 100644 index e211c1e0..00000000 --- a/.github/workflows/avd-kernel.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: GKI Kernel Build - -on: - workflow_call: - inputs: - version_name: - required: true - type: string - description: > - With SUBLEVEL of kernel, - for example: android12-5.10.66 - arch: - required: true - type: string - description: > - Build arch: aarch64/x86_64 - debug: - required: false - type: boolean - default: true - manifest_name: - required: false - type: string - description: > - Local repo manifest xml path, - typically for AVD kernel build. - secrets: - BOOT_SIGN_KEY: - required: false - CHAT_ID: - required: false - BOT_TOKEN: - required: false - MESSAGE_THREAD_ID: - required: false - -jobs: - build: - name: Build ${{ inputs.version_name }} - runs-on: ubuntu-22.04 - steps: - - name: Maximize build space - uses: easimon/maximize-build-space@master - with: - root-reserve-mb: 8192 - temp-reserve-mb: 2048 - remove-dotnet: 'true' - remove-android: 'true' - remove-haskell: 'true' - remove-codeql: 'true' - - - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: Setup need_upload - id: need_upload - run: | - if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then - echo "UPLOAD=true" >> $GITHUB_OUTPUT - else - echo "UPLOAD=false" >> $GITHUB_OUTPUT - fi - - - name: Setup kernel source - run: | - echo "Free space:" - df -h - cd $GITHUB_WORKSPACE - sudo apt-get install repo -y - mkdir android-kernel && cd android-kernel - repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -m "$GITHUB_WORKSPACE/KernelSU/.github/manifests/${{ inputs.manifest_name }}" --repo-rev=v2.16 - repo --version - repo --trace sync -c -j$(nproc --all) --no-tags - df -h - - - name: Setup KernelSU - env: - PATCH_PATH: ${{ inputs.patch_path }} - IS_DEBUG_KERNEL: ${{ inputs.debug }} - run: | - cd $GITHUB_WORKSPACE/android-kernel - echo "[+] KernelSU setup" - GKI_ROOT=$(pwd) - echo "[+] GKI_ROOT: $GKI_ROOT" - echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers" - ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu - echo "[+] Add KernelSU driver to Makefile" - DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile - DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig - grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" - grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" - echo "[+] Apply KernelSU patches" - cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found" - - if [ "$IS_DEBUG_KERNEL" = "true" ]; then - echo "[+] Enable debug features for kernel" - printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile - fi - repo status - echo "[+] KernelSU setup done." - cd $GITHUB_WORKSPACE/KernelSU - VERSION=$(($(git rev-list --count HEAD) + 10200)) - echo "VERSION: $VERSION" - echo "kernelsu_version=$VERSION" >> $GITHUB_ENV - - - name: Make working directory clean to avoid dirty - working-directory: android-kernel - run: | - rm common/android/abi_gki_protected_exports_* || echo "No protected exports!" - git config --global user.email "bot@kernelsu.org" - git config --global user.name "KernelSUBot" - cd common/ && git add -A && git commit -a -m "Add KernelSU" - repo status - - - name: Build kernel - working-directory: android-kernel - run: | - if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then - export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }} - export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }} - fi - tools/bazel run --config=fast --config=stamp --lto=thin //common-modules/virtual-device:virtual_device_${{ inputs.arch }}_dist -- --dist_dir=dist - NAME=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }} - TARGET_IMAGE=dist/bzImage - if [ ! -e $TARGET_IMAGE ]; then - TARGET_IMAGE=dist/Image - fi - mv $TARGET_IMAGE $NAME - echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV - - - name: Upload Kernel - uses: actions/upload-artifact@v4 - with: - name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }} - path: "${{ env.file_path }}" diff --git a/.github/workflows/build-lkm-local.yml b/.github/workflows/build-lkm-local.yml deleted file mode 100644 index aba49f42..00000000 --- a/.github/workflows/build-lkm-local.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Build LKM for KernelSU Local -on: - workflow_call: - inputs: - upload: - required: true - type: boolean - default: true - description: "Whether to upload to branch" - secrets: - # username:github_pat - TOKEN: - required: true - workflow_dispatch: - inputs: - upload: - required: true - type: boolean - default: true - description: "Whether to upload to branch" -jobs: - build-lkm: - strategy: - matrix: - include: - - version: "android12-5.10" - sub_level: 236 - os_patch_level: 2025-05 - - version: "android13-5.10" - sub_level: 234 - os_patch_level: 2025-03 - - version: "android13-5.15" - sub_level: 178 - os_patch_level: 2025-03 - - version: "android14-5.15" - sub_level: 178 - os_patch_level: 2025-03 - - version: "android14-6.1" - sub_level: 134 - os_patch_level: 2025-05 - - version: "android15-6.6" - sub_level: 87 - os_patch_level: 2025-05 - # uses: ./.github/workflows/gki-kernel-mock.yml when debugging - uses: ./.github/workflows/gki-kernel-local.yml - with: - version: ${{ matrix.version }} - version_name: ${{ matrix.version }}.${{ matrix.sub_level }} - tag: ${{ matrix.version }}-${{ matrix.os_patch_level }} - os_patch_level: ${{ matrix.os_patch_level }} - build_lkm: true - - push-to-branch: - needs: [build-lkm] - runs-on: self-hosted - if: ${{ inputs.upload }} - steps: - - name: Download all workflow run artifacts - uses: actions/download-artifact@v4 - with: - path: bin/ - merge-multiple: true - - name: Push to branch LKM - run: | - cd bin - git config --global init.defaultBranch lkm - git init - git remote add origin https://${{ secrets.TOKEN }}@github.com/${{ github.repository }} - git config --local user.name "github-actions[bot]" - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - find . -type f - git add . - git commit -m "Upload LKM from ${{ github.sha }}" -m "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - git push --force --set-upstream origin lkm diff --git a/.github/workflows/build-lkm.yml b/.github/workflows/build-lkm.yml index 243f0305..97f3da87 100644 --- a/.github/workflows/build-lkm.yml +++ b/.github/workflows/build-lkm.yml @@ -41,6 +41,9 @@ jobs: - version: "android15-6.6" sub_level: 98 os_patch_level: 2025-09 + - version: "android16-6.12" + sub_level: 38 + os_patch_level: 2025-09 # uses: ./.github/workflows/gki-kernel-mock.yml when debugging uses: ./.github/workflows/gki-kernel.yml with: diff --git a/.github/workflows/build-manager-manual.yml b/.github/workflows/build-manager-manual.yml deleted file mode 100644 index ff22257f..00000000 --- a/.github/workflows/build-manager-manual.yml +++ /dev/null @@ -1,245 +0,0 @@ -name: Build Manager Manual - -on: - workflow_dispatch: - inputs: - build_lkm: - required: true - type: choice - default: "auto" - options: - - "true" - - "false" - - "auto" - description: "Whether to build lkm" - upload_lkm: - required: true - type: boolean - default: true - description: "Whether to upload lkm" -jobs: - check-build-lkm: - runs-on: self-hosted - outputs: - build_lkm: ${{ steps.check-build.outputs.build_lkm }} - upload_lkm: ${{ steps.check-build.outputs.upload_lkm }} - steps: - - name: check build - id: check-build - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.build_lkm }}" != "auto" ]; then - kernel_changed="${{ inputs.build_lkm }}" - else - kernel_changed=true - mkdir tmp - cd tmp - git config --global init.defaultBranch bot - git config --global user.name 'Bot' - git config --global user.email 'bot@github.shirkneko.io' - git init . - git remote add origin https://github.com/${{ github.repository }} - CURRENT_COMMIT="${{ github.event.head_commit.id }}" - git fetch origin $CURRENT_COMMIT --depth=1 - git fetch origin lkm --depth=1 - LKM_COMMIT="$(git log --format=%B -n 1 origin/lkm | head -n 1)" - LKM_COMMIT="${LKM_COMMIT#Upload LKM from }" - LKM_COMMIT=$(echo "$LKM_COMMIT" | tr -d '[:space:]') - echo "LKM_COMMIT=$LKM_COMMIT" - git fetch origin "$LKM_COMMIT" --depth=1 - git diff --quiet "$LKM_COMMIT" "$CURRENT_COMMIT" -- kernel :!kernel/setup.sh .github/workflows/build-lkm-local.yml .github/workflows/build-kernel-*.yml && kernel_changed=false - cd .. - rm -rf tmp - fi - if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then - need_upload=true - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - need_upload="${{ inputs.upload_lkm }}" - else - need_upload=false - fi - echo "kernel changed: $kernel_changed" - echo "need upload: $need_upload" - echo "build_lkm=$kernel_changed" >> "$GITHUB_OUTPUT" - echo "upload_lkm=$need_upload" >> "$GITHUB_OUTPUT" - - build-lkm: - needs: check-build-lkm - uses: ./.github/workflows/build-lkm-local.yml - if: ${{ needs.check-build-lkm.outputs.build_lkm == 'true' }} - with: - upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }} - secrets: inherit - build-susfs: - if: ${{ always() }} - needs: [ check-build-lkm, build-lkm ] - strategy: - matrix: - include: - - target: aarch64-linux-android - os: ubuntu-latest - uses: ./.github/workflows/susfs.yml - with: - target: ${{ matrix.target }} - os: ${{ matrix.os }} - - build-kpmmgr: - if: ${{ always() }} - needs: [ check-build-lkm, build-lkm ] - strategy: - matrix: - include: - - target: aarch64-linux-android - os: ubuntu-latest - uses: ./.github/workflows/kpmmgr.yml - with: - target: ${{ matrix.target }} - os: ${{ matrix.os }} - - build-ksud: - if: ${{ always() }} - needs: [ check-build-lkm, build-lkm ] - strategy: - matrix: - include: - - target: aarch64-linux-android - os: ubuntu-latest - - target: x86_64-linux-android - os: ubuntu-latest - - target: armv7-linux-androideabi - os: ubuntu-latest - uses: ./.github/workflows/ksud.yml - with: - target: ${{ matrix.target }} - os: ${{ matrix.os }} - pack_lkm: true - pull_lkm: ${{ needs.check-build-lkm.outputs.build_lkm != 'true' }} - - build-manager: - if: ${{ always() }} - needs: build-ksud - runs-on: self-hosted - defaults: - run: - working-directory: ./manager - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup need_upload - id: need_upload - run: | - if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then - echo "UPLOAD=true" >> $GITHUB_OUTPUT - else - echo "UPLOAD=false" >> $GITHUB_OUTPUT - fi - - - name: Write key - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref == 'refs/heads/susfs' || github.ref_type == 'tag' }} - run: | - if [ ! -z "${{ secrets.KEYSTORE }}" ]; then - { - echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}' - echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}' - echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' - echo KEYSTORE_FILE='key.jks' - } >> gradle.properties - echo "${{ secrets.KEYSTORE }}" | base64 -d > key.jks - fi - - - name: Download arm64 susfs - uses: actions/download-artifact@v4 - with: - name: susfs-aarch64-linux-android - path: . - - - name: Download arm64 kpmmgr - uses: actions/download-artifact@v4 - with: - name: kpmmgr-aarch64-linux-android - path: . - - - name: Download arm64 ksud - uses: actions/download-artifact@v4 - with: - name: ksud-aarch64-linux-android - path: . - - - name: Download x86_64 ksud - uses: actions/download-artifact@v4 - with: - name: ksud-x86_64-linux-android - path: . - - - name: Download arm ksud - uses: actions/download-artifact@v4 - with: - name: ksud-armv7-linux-androideabi - path: . - - - name: Copy ksud to app jniLibs - run: | - mkdir -p app/src/main/jniLibs/arm64-v8a - mkdir -p app/src/main/jniLibs/x86_64 - mkdir -p app/src/main/jniLibs/armeabi-v7a - cp -f ../aarch64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozako.so - cp -f ../x86_64-linux-android/release/zakozako ../manager/app/src/main/jniLibs/x86_64/libzakozako.so - cp -f ../armv7-linux-androideabi/release/zakozako ../manager/app/src/main/jniLibs/armeabi-v7a/libzakozako.so - - - name: Copy kpmmgr to app jniLibs - run: | - mkdir -p app/src/main/jniLibs/arm64-v8a - cp -f ../arm64-v8a/kpmmgr ../manager/app/src/main/jniLibs/arm64-v8a/libkpmmgr.so - - - name: Copy susfs to app jniLibs - run: | - mkdir -p app/src/main/jniLibs/arm64-v8a - cp -f ../arm64-v8a/zakozakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozakozako.so - - - name: Build with Gradle - run: | - export ANDROID_HOME=/root/.android/sdk - export PATH=$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$PATH - ./gradlew clean assembleRelease - - - name: Upload build artifact - uses: actions/upload-artifact@v4 - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} - with: - name: manager - path: manager/app/build/outputs/apk/release/*.apk - - - name: Upload mappings - uses: actions/upload-artifact@v4 - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} - with: - name: "mappings" - path: "manager/app/build/outputs/mapping/release/" - - - name: Bot session cache - if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' - id: bot_session_cache - uses: actions/cache@v4 - with: - path: scripts/ksubot.session - key: ${{ runner.os }}-bot-session - - - name: Upload to telegram - if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' - env: - CHAT_ID: ${{ vars.CHAT_ID }} - BOT_TOKEN: ${{ secrets.BOT_TOKEN }} - MESSAGE_THREAD_ID: ${{ vars.MESSAGE_THREAD_ID }} - COMMIT_MESSAGE: ${{ github.event.head_commit.message }} - COMMIT_URL: ${{ github.event.head_commit.url }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - TITLE: Manager - run: | - if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then - export VERSION=$(git rev-list --count HEAD) - APK=$(find ./app/build/outputs/apk/release -name "*.apk") - python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK - fi \ No newline at end of file diff --git a/.github/workflows/gki-kernel-local.yml b/.github/workflows/gki-kernel-local.yml deleted file mode 100644 index f46d77bf..00000000 --- a/.github/workflows/gki-kernel-local.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: GKI Kernel Build Local - -on: - workflow_call: - inputs: - version: - required: true - type: string - description: > - Output directory of gki, - for example: android12-5.10 - version_name: - required: true - type: string - description: > - With SUBLEVEL of kernel, - for example: android12-5.10.66 - tag: - required: true - type: string - description: > - Part of branch name of common kernel manifest, - for example: android12-5.10-2021-11 - os_patch_level: - required: false - type: string - description: > - Patch level of common kernel manifest, - for example: 2021-11 - default: 2022-05 - patch_path: - required: false - type: string - description: > - Directory name of .github/patches/ - for example: 5.10 - use_cache: - required: false - type: boolean - default: true - embed_ksud: - required: false - type: string - default: ksud-aarch64-linux-android - description: > - Artifact name of prebuilt ksud to be embedded - for example: ksud-aarch64-linux-android - debug: - required: false - type: boolean - default: false - build_lkm: - required: false - type: boolean - default: false - secrets: - BOOT_SIGN_KEY: - required: false - CHAT_ID: - required: false - BOT_TOKEN: - required: false - MESSAGE_THREAD_ID: - required: false - -jobs: - build: - name: Build ${{ inputs.version_name }} - runs-on: self-hosted - env: - CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion" - CCACHE_NOHASHDIR: "true" - CCACHE_HARDLINK: "true" - steps: - - uses: actions/checkout@v4 - with: - path: KernelSU - fetch-depth: 0 - - - name: Setup need_upload - id: need_upload - run: | - if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then - echo "UPLOAD=true" >> $GITHUB_OUTPUT - else - echo "UPLOAD=false" >> $GITHUB_OUTPUT - fi - - - name: Setup kernel source - run: | - echo "Free space:" - df -h - cd $GITHUB_WORKSPACE - sudo apt-get install repo -y - export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo' - mkdir android-kernel && cd android-kernel - repo init --depth=1 --u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.35 - REMOTE_BRANCH=$(git ls-remote https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/kernel/common ${{ inputs.tag }}) - DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml - if grep -q deprecated <<< $REMOTE_BRANCH; then - echo "Found deprecated branch: ${{ inputs.tag }}" - sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' $DEFAULT_MANIFEST_PATH - cat $DEFAULT_MANIFEST_PATH - fi - repo --version - repo --trace sync -c -j$(nproc --all) --no-tags - df -h - - - name: Setup KernelSU - env: - PATCH_PATH: ${{ inputs.patch_path }} - IS_DEBUG_KERNEL: ${{ inputs.debug }} - run: | - cd $GITHUB_WORKSPACE/android-kernel - echo "[+] KernelSU setup" - GKI_ROOT=$(pwd) - echo "[+] GKI_ROOT: $GKI_ROOT" - echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers" - ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu - echo "[+] Add KernelSU driver to Makefile" - DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile - DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig - grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE" - grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG" - echo "[+] Apply Compilation Patches" - if [ ! -e build/build.sh ]; then - GLIBC_VERSION=$(ldd --version 2>/dev/null | head -n 1 | awk '{print $NF}') - echo "GLIBC_VERSION: $GLIBC_VERSION" - if [ "$(printf '%s\n' "2.38" "$GLIBC_VERSION" | sort -V | head -n1)" = "2.38" ]; then - echo "Patching resolve_btfids/Makefile" - cd $GKI_ROOT/common/ && sed -i '/\$(Q)\$(MAKE) -C \$(SUBCMD_SRC) OUTPUT=\$(abspath \$(dir \$@))\/ \$(abspath \$@)/s//$(Q)$(MAKE) -C $(SUBCMD_SRC) EXTRA_CFLAGS="$(CFLAGS)" OUTPUT=$(abspath $(dir $@))\/ $(abspath $@)/' tools/bpf/resolve_btfids/Makefile || echo "No patch needed." - fi - fi - - if [ "$IS_DEBUG_KERNEL" = "true" ]; then - echo "[+] Enable debug features for kernel" - printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile - fi - repo status - echo "[+] KernelSU setup done." - - - name: Symbol magic - run: | - echo "[+] Export all symbol from abi_gki_aarch64.xml" - COMMON_ROOT=$GITHUB_WORKSPACE/android-kernel/common - KSU_ROOT=$GITHUB_WORKSPACE/KernelSU - ABI_XML=$COMMON_ROOT/android/abi_gki_aarch64.xml - SYMBOL_LIST=$COMMON_ROOT/android/abi_gki_aarch64 - # python3 $KSU_ROOT/scripts/abi_gki_all.py $ABI_XML > $SYMBOL_LIST - echo "[+] Add KernelSU symbols" - cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST - - - name: Setup ccache - if: inputs.use_cache == true - uses: hendrikmuhs/ccache-action@v1 - with: - key: gki-kernel-aarch64-${{ inputs.version_name }} - max-size: 2G - save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - - - name: Setup for LKM - if: ${{ inputs.build_lkm == true }} - working-directory: android-kernel - run: | - pip install ast-grep-cli - sudo apt-get install llvm-15 -y - ast-grep -U -p '$$$ check_exports($$$) {$$$}' -r '' common/scripts/mod/modpost.c - ast-grep -U -p 'check_exports($$$);' -r '' common/scripts/mod/modpost.c - sed -i '/config KSU/,/help/{s/default y/default m/}' common/drivers/kernelsu/Kconfig - echo "drivers/kernelsu/kernelsu.ko" >> common/android/gki_aarch64_modules - - # bazel build, android14-5.15, android14-6.1 use bazel - if [ ! -e build/build.sh ]; then - sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found" - if [ -e common/modules.bzl ]; then - sed -i 's/_COMMON_GKI_MODULES_LIST = \[/_COMMON_GKI_MODULES_LIST = \[ "drivers\/kernelsu\/kernelsu.ko",/g' common/modules.bzl - fi - else - TARGET_FILE="build/kernel/build.sh" - if [ ! -e "$TARGET_FILE" ]; then - TARGET_FILE="build/build.sh" - fi - sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' $TARGET_FILE || echo "No unknown symbol in $TARGET_FILE" - sed -i 's/if ! diff -u "\${KERNEL_DIR}\/\${MODULES_ORDER}" "\${OUT_DIR}\/modules\.order"; then/if false; then/g' $TARGET_FILE - sed -i 's@${ROOT_DIR}/build/abi/compare_to_symbol_list@echo@g' $TARGET_FILE - sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found" - fi - - - name: Make working directory clean to avoid dirty - working-directory: android-kernel - run: | - if [ -e common/BUILD.bazel ]; then - sed -i '/^[[:space:]]*"protected_exports_list"[[:space:]]*:[[:space:]]*"android\/abi_gki_protected_exports_aarch64",$/d' common/BUILD.bazel - fi - rm common/android/abi_gki_protected_exports_* || echo "No protected exports!" - git config --global user.email "bot@kernelsu.org" - git config --global user.name "KernelSUBot" - cd common/ && git add -A && git commit -a -m "Add KernelSU" - repo status - - - name: Build Kernel/LKM - working-directory: android-kernel - run: | - if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then - export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }} - export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }} - fi - if [ -e build/build.sh ]; then - LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh CC="/usr/bin/ccache clang" - else - tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist - fi - - - name: Prepare artifacts - id: prepareArtifacts - run: | - OUTDIR=android-kernel/out/${{ inputs.version }}/dist - if [ ! -e $OUTDIR ]; then - OUTDIR=android-kernel/dist - fi - mkdir output - if [ "${{ inputs.build_lkm}}" = "true" ]; then - llvm-strip-15 -d $OUTDIR/kernelsu.ko - mv $OUTDIR/kernelsu.ko ./output/${{ inputs.version }}_kernelsu.ko - else - cp $OUTDIR/Image ./output/ - cp $OUTDIR/Image.lz4 ./output/ - git clone https://github.com/Kernel-SU/AnyKernel3 - rm -rf ./AnyKernel3/.git - cp $OUTDIR/Image ./AnyKernel3/ - fi - - - name: Upload Image and Image.gz - uses: actions/upload-artifact@v4 - if: ${{ inputs.build_lkm == false }} - with: - name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }} - path: ./output/* - - - name: Upload AnyKernel3 - if: ${{ inputs.build_lkm == false }} - uses: actions/upload-artifact@v4 - with: - name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }} - path: ./AnyKernel3/* - - - name: Upload LKM - uses: actions/upload-artifact@v4 - if: ${{ inputs.build_lkm == true }} - with: - name: ${{ inputs.version }}-lkm - path: ./output/*_kernelsu.ko diff --git a/kernel/Makefile b/kernel/Makefile index a87969d3..249c9dbd 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -7,6 +7,7 @@ kernelsu-objs += pkg_observer.o kernelsu-objs += throne_tracker.o kernelsu-objs += core_hook.o kernelsu-objs += supercalls.o +kernelsu-objs += feature.o kernelsu-objs += ksud.o kernelsu-objs += embed_ksud.o kernelsu-objs += kernel_compat.o diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 470bb54b..fbce0dc5 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -31,6 +31,7 @@ #include "allowlist.h" #include "arch.h" #include "core_hook.h" +#include "feature.h" #include "klog.h" // IWYU pragma: keep #include "ksu.h" #include "ksud.h" @@ -78,6 +79,29 @@ struct ksu_umount_work { struct mnt_namespace *mnt_ns; }; +static bool ksu_kernel_umount_enabled = true; + +static int kernel_umount_feature_get(u64 *value) +{ + *value = ksu_kernel_umount_enabled ? 1 : 0; + return 0; +} + +static int kernel_umount_feature_set(u64 value) +{ + bool enable = value != 0; + ksu_kernel_umount_enabled = enable; + pr_info("kernel_umount: set to %d\n", enable); + return 0; +} + +static const struct ksu_feature_handler kernel_umount_handler = { + .feature_id = KSU_FEATURE_KERNEL_UMOUNT, + .name = "kernel_umount", + .get_handler = kernel_umount_feature_get, + .set_handler = kernel_umount_feature_set, +}; + static inline bool is_allow_su() { if (is_manager()) { @@ -539,8 +563,8 @@ static void do_umount_work(struct work_struct *work) // try umount ksu temp path try_umount("/debug_ramdisk", false, MNT_DETACH); + // fixme: dec refcount current->nsproxy->mnt_ns = old_mnt_ns; - put_mnt_ns(umount_work->mnt_ns); kfree(umount_work); } @@ -588,6 +612,10 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) return 0; } + if (!ksu_kernel_umount_enabled) { + return 0; + } + if (!ksu_uid_should_umount(new_uid.val)) { return 0; } else { @@ -622,8 +650,8 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) return 0; } + // fixme: inc refcount umount_work->mnt_ns = current->nsproxy->mnt_ns; - get_mnt_ns(umount_work->mnt_ns); INIT_WORK(&umount_work->work, do_umount_work); @@ -668,9 +696,8 @@ static struct kprobe reboot_kp = { // 2. security_task_fix_setuid hook for handling setuid static int security_task_fix_setuid_handler_pre(struct kprobe *p, struct pt_regs *regs) { - struct pt_regs *real_regs = PT_REAL_REGS(regs); - struct cred *new = (struct cred *)PT_REGS_PARM1(real_regs); - const struct cred *old = (const struct cred *)PT_REGS_PARM2(real_regs); + struct cred *new = (struct cred *)PT_REGS_PARM1(regs); + const struct cred *old = (const struct cred *)PT_REGS_PARM2(regs); ksu_handle_setuid(new, old); @@ -844,6 +871,10 @@ __maybe_unused int ksu_kprobe_exit(void) void __init ksu_core_init(void) { + if (ksu_register_feature_handler(&kernel_umount_handler)) { + pr_err("Failed to register kernel_umount feature handler\n"); + } + ksu_workqueue = alloc_workqueue("ksu_umount", WQ_UNBOUND, 0); if (!ksu_workqueue) { pr_err("Failed to create ksu workqueue\n"); diff --git a/kernel/feature.c b/kernel/feature.c new file mode 100644 index 00000000..1c2a004e --- /dev/null +++ b/kernel/feature.c @@ -0,0 +1,188 @@ +#include "feature.h" +#include "klog.h" // IWYU pragma: keep + +#include +#include + +static struct ksu_feature_handler *feature_handlers[KSU_FEATURE_MAX]; + +static DEFINE_MUTEX(feature_mutex); + +int ksu_register_feature_handler(const struct ksu_feature_handler *handler) +{ + int ret = 0; + + if (!handler) { + pr_err("feature: register handler is NULL\n"); + return -EINVAL; + } + + if (handler->feature_id >= KSU_FEATURE_MAX) { + pr_err("feature: invalid feature_id %u\n", handler->feature_id); + return -EINVAL; + } + + if (!handler->get_handler && !handler->set_handler) { + pr_err("feature: no handler provided for feature %u\n", handler->feature_id); + return -EINVAL; + } + + mutex_lock(&feature_mutex); + + if (feature_handlers[handler->feature_id]) { + pr_warn("feature: handler for %u already registered, overwriting\n", + handler->feature_id); + } + + feature_handlers[handler->feature_id] = kmalloc(sizeof(struct ksu_feature_handler), GFP_KERNEL); + if (!feature_handlers[handler->feature_id]) { + pr_err("feature: failed to allocate handler for %u\n", handler->feature_id); + ret = -ENOMEM; + goto out; + } + + memcpy(feature_handlers[handler->feature_id], handler, sizeof(struct ksu_feature_handler)); + + pr_info("feature: registered handler for %s (id=%u)\n", + handler->name ? handler->name : "unknown", handler->feature_id); + +out: + mutex_unlock(&feature_mutex); + return ret; +} + +int ksu_unregister_feature_handler(u32 feature_id) +{ + int ret = 0; + + if (feature_id >= KSU_FEATURE_MAX) { + pr_err("feature: invalid feature_id %u\n", feature_id); + return -EINVAL; + } + + mutex_lock(&feature_mutex); + + if (!feature_handlers[feature_id]) { + pr_warn("feature: no handler registered for %u\n", feature_id); + ret = -ENOENT; + goto out; + } + + kfree(feature_handlers[feature_id]); + feature_handlers[feature_id] = NULL; + + pr_info("feature: unregistered handler for id=%u\n", feature_id); + +out: + mutex_unlock(&feature_mutex); + return ret; +} + +int ksu_get_feature(u32 feature_id, u64 *value, bool *supported) +{ + int ret = 0; + struct ksu_feature_handler *handler; + + if (feature_id >= KSU_FEATURE_MAX) { + pr_err("feature: invalid feature_id %u\n", feature_id); + return -EINVAL; + } + + if (!value || !supported) { + pr_err("feature: invalid parameters\n"); + return -EINVAL; + } + + mutex_lock(&feature_mutex); + + handler = feature_handlers[feature_id]; + + if (!handler) { + *supported = false; + *value = 0; + pr_debug("feature: feature %u not supported\n", feature_id); + goto out; + } + + *supported = true; + + if (!handler->get_handler) { + pr_warn("feature: no get_handler for feature %u\n", feature_id); + ret = -EOPNOTSUPP; + goto out; + } + + ret = handler->get_handler(value); + if (ret) { + pr_err("feature: get_handler for %u failed: %d\n", feature_id, ret); + } + +out: + mutex_unlock(&feature_mutex); + return ret; +} + +int ksu_set_feature(u32 feature_id, u64 value) +{ + int ret = 0; + struct ksu_feature_handler *handler; + + if (feature_id >= KSU_FEATURE_MAX) { + pr_err("feature: invalid feature_id %u\n", feature_id); + return -EINVAL; + } + + mutex_lock(&feature_mutex); + + handler = feature_handlers[feature_id]; + + if (!handler) { + pr_err("feature: feature %u not registered\n", feature_id); + ret = -EOPNOTSUPP; + goto out; + } + + if (!handler->set_handler) { + pr_warn("feature: no set_handler for feature %u\n", feature_id); + ret = -EOPNOTSUPP; + goto out; + } + + ret = handler->set_handler(value); + if (ret) { + pr_err("feature: set_handler for %u failed: %d\n", feature_id, ret); + } + +out: + mutex_unlock(&feature_mutex); + return ret; +} + +void ksu_feature_init(void) +{ + int i; + + for (i = 0; i < KSU_FEATURE_MAX; i++) { + feature_handlers[i] = NULL; + } + + pr_info("feature: feature management initialized\n"); +} + +void ksu_feature_exit(void) +{ + int i; + + mutex_lock(&feature_mutex); + + for (i = 0; i < KSU_FEATURE_MAX; i++) { + if (feature_handlers[i]) { + kfree(feature_handlers[i]); + feature_handlers[i] = NULL; + } + } + + mutex_unlock(&feature_mutex); + + pr_info("feature: feature management cleaned up\n"); +} diff --git a/kernel/feature.h b/kernel/feature.h new file mode 100644 index 00000000..202c25e3 --- /dev/null +++ b/kernel/feature.h @@ -0,0 +1,35 @@ +#ifndef __KSU_H_FEATURE +#define __KSU_H_FEATURE + +#include + +enum ksu_feature_id { + KSU_FEATURE_SU_COMPAT = 0, + KSU_FEATURE_KERNEL_UMOUNT = 1, + + KSU_FEATURE_MAX +}; + +typedef int (*ksu_feature_get_t)(u64 *value); +typedef int (*ksu_feature_set_t)(u64 value); + +struct ksu_feature_handler { + u32 feature_id; + const char *name; + ksu_feature_get_t get_handler; + ksu_feature_set_t set_handler; +}; + +int ksu_register_feature_handler(const struct ksu_feature_handler *handler); + +int ksu_unregister_feature_handler(u32 feature_id); + +int ksu_get_feature(u32 feature_id, u64 *value, bool *supported); + +int ksu_set_feature(u32 feature_id, u64 value); + +void ksu_feature_init(void); + +void ksu_feature_exit(void); + +#endif // __KSU_H_FEATURE diff --git a/kernel/ksu.c b/kernel/ksu.c index 0f657826..1675c84d 100644 --- a/kernel/ksu.c +++ b/kernel/ksu.c @@ -8,6 +8,7 @@ #include "allowlist.h" #include "arch.h" #include "core_hook.h" +#include "feature.h" #include "klog.h" // IWYU pragma: keep #include "ksu.h" #include "throne_tracker.h" @@ -26,6 +27,7 @@ extern void ksu_sucompat_init(); extern void ksu_sucompat_exit(); extern void ksu_ksud_init(); extern void ksu_ksud_exit(); +extern void ksu_supercalls_init(); #ifdef CONFIG_KSU_TRACEPOINT_HOOK extern void ksu_trace_register(); extern void ksu_trace_unregister(); @@ -43,6 +45,10 @@ int __init kernelsu_init(void) pr_alert("*************************************************************"); #endif + ksu_feature_init(); + + ksu_supercalls_init(); + ksu_core_init(); ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0); @@ -89,6 +95,7 @@ void kernelsu_exit(void) #endif ksu_core_exit(); + ksu_feature_exit(); } module_init(kernelsu_init); diff --git a/kernel/sucompat.c b/kernel/sucompat.c index d935dafe..b710df6c 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -13,6 +13,7 @@ #include "objsec.h" #include "allowlist.h" #include "arch.h" +#include "feature.h" #include "klog.h" // IWYU pragma: keep #include "ksud.h" #include "kernel_compat.h" @@ -22,6 +23,44 @@ #define SH_PATH "/system/bin/sh" extern void escape_to_root(); +void ksu_sucompat_enable(); +void ksu_sucompat_disable(); + +bool ksu_su_compat_enabled = true; + +static int su_compat_feature_get(u64 *value) +{ + *value = ksu_su_compat_enabled ? 1 : 0; + return 0; +} + +static int su_compat_feature_set(u64 value) +{ + bool enable = value != 0; + + if (enable == ksu_su_compat_enabled) { + pr_info("su_compat: no need to change\n"); + return 0; + } + + if (enable) { + ksu_sucompat_enable(); + } else { + ksu_sucompat_disable(); + } + + ksu_su_compat_enabled = enable; + pr_info("su_compat: set to %d\n", enable); + + return 0; +} + +static const struct ksu_feature_handler su_compat_handler = { + .feature_id = KSU_FEATURE_SU_COMPAT, + .name = "su_compat", + .get_handler = su_compat_feature_get, + .set_handler = su_compat_feature_set, +}; #ifndef CONFIG_KSU_KPROBES_HOOK static bool ksu_sucompat_hook_state __read_mostly = true; @@ -340,7 +379,7 @@ static void destroy_kprobe(struct kprobe **kp_ptr) #endif // sucompat: permited process can execute 'su' to gain root access. -void ksu_sucompat_init() +void ksu_sucompat_enable() { #ifdef CONFIG_KSU_KPROBES_HOOK su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); @@ -353,7 +392,7 @@ void ksu_sucompat_init() #endif } -void ksu_sucompat_exit() +void ksu_sucompat_disable() { #ifdef CONFIG_KSU_KPROBES_HOOK int i; @@ -365,3 +404,23 @@ void ksu_sucompat_exit() pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n"); #endif } + +// sucompat: permited process can execute 'su' to gain root access. +void ksu_sucompat_init() +{ + if (ksu_register_feature_handler(&su_compat_handler)) { + pr_err("Failed to register su_compat feature handler\n"); + } + if (ksu_su_compat_enabled) { + ksu_sucompat_enable(); + } +} + +void ksu_sucompat_exit() +{ + if (ksu_su_compat_enabled) { + ksu_sucompat_disable(); + } + ksu_unregister_feature_handler(KSU_FEATURE_SU_COMPAT); +} + diff --git a/kernel/supercalls.c b/kernel/supercalls.c index 22b5cb08..77b5218c 100644 --- a/kernel/supercalls.c +++ b/kernel/supercalls.c @@ -11,6 +11,7 @@ #include #include "allowlist.h" +#include "feature.h" #include "klog.h" // IWYU pragma: keep #include "ksud.h" #include "manager.h" @@ -32,29 +33,25 @@ extern int handle_sepolicy(unsigned long arg3, void __user *arg4); extern void ksu_sucompat_init(void); extern void ksu_sucompat_exit(void); -// Forward declaration for anon_ksu_fops -static const struct file_operations anon_ksu_fops; - -static bool ksu_su_compat_enabled = true; bool ksu_uid_scanner_enabled = false; // Permission check functions -bool perm_check_manager(void) +bool only_manager(void) { return is_manager(); } -bool perm_check_root(void) +bool only_root(void) { return current_uid().val == 0; } -bool perm_check_basic(void) +bool manager_or_root(void) { return current_uid().val == 0 || is_manager(); } -bool perm_check_all(void) +bool always_allow(void) { return true; // No permission check } @@ -101,6 +98,7 @@ static int do_get_info(void __user *arg) if (is_manager()) { cmd.flags |= 0x2; } + cmd.features = KSU_FEATURE_MAX; if (copy_to_user(arg, &cmd, sizeof(cmd))) { pr_err("get_version: copy_to_user failed\n"); @@ -318,42 +316,49 @@ static int do_set_app_profile(void __user *arg) return 0; } -static int do_is_su_enabled(void __user *arg) +static int do_get_feature(void __user *arg) { - struct ksu_is_su_enabled_cmd cmd; + struct ksu_get_feature_cmd cmd; + bool supported; + int ret; - cmd.enabled = ksu_su_compat_enabled; + if (copy_from_user(&cmd, arg, sizeof(cmd))) { + pr_err("get_feature: copy_from_user failed\n"); + return -EFAULT; + } + + ret = ksu_get_feature(cmd.feature_id, &cmd.value, &supported); + cmd.supported = supported ? 1 : 0; + + if (ret && supported) { + pr_err("get_feature: failed for feature %u: %d\n", cmd.feature_id, ret); + return ret; + } if (copy_to_user(arg, &cmd, sizeof(cmd))) { - pr_err("is_su_enabled: copy_to_user failed\n"); + pr_err("get_feature: copy_to_user failed\n"); return -EFAULT; } return 0; } -static int do_enable_su(void __user *arg) +static int do_set_feature(void __user *arg) { - struct ksu_enable_su_cmd cmd; + struct ksu_set_feature_cmd cmd; + int ret; if (copy_from_user(&cmd, arg, sizeof(cmd))) { - pr_err("enable_su: copy_from_user failed\n"); + pr_err("set_feature: copy_from_user failed\n"); return -EFAULT; } - if (cmd.enable == ksu_su_compat_enabled) { - pr_info("enable_su: no need to change\n"); - return 0; + ret = ksu_set_feature(cmd.feature_id, cmd.value); + if (ret) { + pr_err("set_feature: failed for feature %u: %d\n", cmd.feature_id, ret); + return ret; } - if (cmd.enable) { - ksu_sucompat_init(); - } else { - ksu_sucompat_exit(); - } - - ksu_su_compat_enabled = cmd.enable; - return 0; } @@ -517,80 +522,79 @@ static int do_enable_uid_scanner(void __user *arg) // IOCTL handlers mapping table static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { - { .cmd = KSU_IOCTL_GRANT_ROOT, .handler = do_grant_root, .perm_check = perm_check_basic, .name = "do_grant_root"}, - { .cmd = KSU_IOCTL_GET_INFO, .handler = do_get_info, .perm_check = perm_check_all, .name = "do_get_info"}, - { .cmd = KSU_IOCTL_REPORT_EVENT, .handler = do_report_event, .perm_check = perm_check_root, .name = "do_report_event"}, - { .cmd = KSU_IOCTL_SET_SEPOLICY, .handler = do_set_sepolicy, .perm_check = perm_check_root, .name = "do_set_sepolicy"}, - { .cmd = KSU_IOCTL_CHECK_SAFEMODE, .handler = do_check_safemode, .perm_check = perm_check_all, .name = "do_check_safemode"}, - { .cmd = KSU_IOCTL_GET_ALLOW_LIST, .handler = do_get_allow_list, .perm_check = perm_check_basic, .name = "do_get_allow_list"}, - { .cmd = KSU_IOCTL_GET_DENY_LIST, .handler = do_get_deny_list, .perm_check = perm_check_basic, .name = "do_get_deny_list"}, - { .cmd = KSU_IOCTL_UID_GRANTED_ROOT, .handler = do_uid_granted_root, .perm_check = perm_check_basic, .name = "do_uid_granted_root"}, - { .cmd = KSU_IOCTL_UID_SHOULD_UMOUNT, .handler = do_uid_should_umount, .perm_check = perm_check_basic, .name = "do_uid_should_umount"}, - { .cmd = KSU_IOCTL_GET_MANAGER_UID, .handler = do_get_manager_uid, .perm_check = perm_check_basic, .name = "do_get_manager_uid"}, - { .cmd = KSU_IOCTL_GET_APP_PROFILE, .handler = do_get_app_profile, .perm_check = perm_check_manager, .name = "do_get_app_profile"}, - { .cmd = KSU_IOCTL_SET_APP_PROFILE, .handler = do_set_app_profile, .perm_check = perm_check_manager, .name = "do_set_app_profile"}, - { .cmd = KSU_IOCTL_IS_SU_ENABLED, .handler = do_is_su_enabled, .perm_check = perm_check_manager, .name = "do_is_su_enabled"}, - { .cmd = KSU_IOCTL_ENABLE_SU, .handler = do_enable_su, .perm_check = perm_check_manager, .name = "do_enable_su"}, - { .cmd = KSU_IOCTL_GET_FULL_VERSION, .handler = do_get_full_version, .perm_check = perm_check_manager, .name = "do_get_full_version"}, - { .cmd = KSU_IOCTL_HOOK_TYPE, .handler = do_get_hook_type, .perm_check = perm_check_basic, .name = "do_get_hook_type"}, - { .cmd = KSU_IOCTL_ENABLE_KPM, .handler = do_enable_kpm, .perm_check = perm_check_basic, .name = "do_enable_kpm"}, - { .cmd = KSU_IOCTL_DYNAMIC_MANAGER, .handler = do_dynamic_manager, .perm_check = perm_check_basic, .name = "do_dynamic_manager"}, - { .cmd = KSU_IOCTL_GET_MANAGERS, .handler = do_get_managers, .perm_check = perm_check_basic, .name = "do_get_managers"}, - { .cmd = KSU_IOCTL_ENABLE_UID_SCANNER, .handler = do_enable_uid_scanner, .perm_check = perm_check_basic, .name = "do_enable_uid_scanner"}, - { .cmd = 0, .handler = NULL, .perm_check = NULL, .name = NULL} // Sentinel + { .cmd = KSU_IOCTL_GRANT_ROOT, .name = "GRANT_ROOT", .handler = do_grant_root, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_GET_INFO, .name = "GET_INFO", .handler = do_get_info, .perm_check = always_allow }, + { .cmd = KSU_IOCTL_REPORT_EVENT, .name = "REPORT_EVENT", .handler = do_report_event, .perm_check = only_root }, + { .cmd = KSU_IOCTL_SET_SEPOLICY, .name = "SET_SEPOLICY", .handler = do_set_sepolicy, .perm_check = only_root }, + { .cmd = KSU_IOCTL_CHECK_SAFEMODE, .name = "CHECK_SAFEMODE", .handler = do_check_safemode, .perm_check = always_allow }, + { .cmd = KSU_IOCTL_GET_ALLOW_LIST, .name = "GET_ALLOW_LIST", .handler = do_get_allow_list, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_GET_DENY_LIST, .name = "GET_DENY_LIST", .handler = do_get_deny_list, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_UID_GRANTED_ROOT, .name = "UID_GRANTED_ROOT", .handler = do_uid_granted_root, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_UID_SHOULD_UMOUNT, .name = "UID_SHOULD_UMOUNT", .handler = do_uid_should_umount, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_GET_MANAGER_UID, .name = "GET_MANAGER_UID", .handler = do_get_manager_uid, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_GET_APP_PROFILE, .name = "GET_APP_PROFILE", .handler = do_get_app_profile, .perm_check = only_manager }, + { .cmd = KSU_IOCTL_SET_APP_PROFILE, .name = "SET_APP_PROFILE", .handler = do_set_app_profile, .perm_check = only_manager }, + { .cmd = KSU_IOCTL_GET_FEATURE, .name = "GET_FEATURE", .handler = do_get_feature, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_GET_FULL_VERSION,.name = "GET_FULL_VERSION", .handler = do_get_full_version, .perm_check = always_allow}, + { .cmd = KSU_IOCTL_HOOK_TYPE,.name = "GET_HOOK_TYPE", .handler = do_get_hook_type, .perm_check = manager_or_root}, + { .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root}, + { .cmd = KSU_IOCTL_DYNAMIC_MANAGER, .name = "SET_DYNAMIC_MANAGER", .handler = do_dynamic_manager, .perm_check = manager_or_root}, + { .cmd = KSU_IOCTL_GET_MANAGERS, .name = "GET_MANAGERS", .handler = do_get_managers, .perm_check = manager_or_root}, + { .cmd = KSU_IOCTL_ENABLE_UID_SCANNER, .name = "SET_ENABLE_UID_SCANNER", .handler = do_enable_uid_scanner, .perm_check = manager_or_root}, + { .cmd = 0, .name = NULL, .handler = NULL, .perm_check = NULL} // Sentine }; +void ksu_supercalls_init(void) +{ + int i; + + pr_info("KernelSU IOCTL Commands:\n"); + for (i = 0; ksu_ioctl_handlers[i].handler; i++) { + pr_info(" %-18s = 0x%08x\n", ksu_ioctl_handlers[i].name, ksu_ioctl_handlers[i].cmd); + } +} + +static inline void ksu_ioctl_audit(unsigned int cmd, const char *cmd_name, uid_t uid, int ret) +{ +#if __SULOG_GATE + const char *result = (ret == 0) ? "SUCCESS" : + (ret == -EPERM) ? "DENIED" : "FAILED"; + ksu_sulog_report_syscall(uid, NULL, cmd_name, result); +#endif +} + // IOCTL dispatcher static long anon_ksu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int i; - const char *cmd_name = "unknown"; - int ret = -ENOTTY; #ifdef CONFIG_KSU_DEBUG pr_info("ksu ioctl: cmd=0x%x from uid=%d\n", cmd, current_uid().val); #endif - // Determine the command name based on the cmd value for (i = 0; ksu_ioctl_handlers[i].handler; i++) { if (cmd == ksu_ioctl_handlers[i].cmd) { - cmd_name = ksu_ioctl_handlers[i].name; - break; + // Check permission first + if (ksu_ioctl_handlers[i].perm_check && + !ksu_ioctl_handlers[i].perm_check()) { + pr_warn("ksu ioctl: permission denied for cmd=0x%x uid=%d\n", + cmd, current_uid().val); + ksu_ioctl_audit(cmd, ksu_ioctl_handlers[i].name, + current_uid().val, -EPERM); + return -EPERM; + } + // Execute handler + int ret = ksu_ioctl_handlers[i].handler(argp); + ksu_ioctl_audit(cmd, ksu_ioctl_handlers[i].name, + current_uid().val, ret); + return ret; } } - // Check permission first - if (ksu_ioctl_handlers[i].perm_check && - !ksu_ioctl_handlers[i].perm_check()) { - pr_warn("ksu ioctl: permission denied for cmd=0x%x uid=%d\n", - cmd, current_uid().val); -#if __SULOG_GATE - ksu_sulog_report_syscall(current_uid().val, NULL, cmd_name, "DENIED"); -#endif - return -EPERM; - } - - // Execute handler - ret = ksu_ioctl_handlers[i].handler(argp); - - // Log the result of the ioctl command - if (ret == 0) { -#if __SULOG_GATE - ksu_sulog_report_syscall(current_uid().val, NULL, cmd_name, "SUCCESS"); -#endif - } else { -#if __SULOG_GATE - ksu_sulog_report_syscall(current_uid().val, NULL, cmd_name, "FAILED"); -#endif - } - - if (ksu_ioctl_handlers[i].handler == NULL) { - pr_warn("ksu ioctl: unsupported command 0x%x\n", cmd); - ret = -ENOTTY; - } - - return ret; + pr_warn("ksu ioctl: unsupported command 0x%x\n", cmd); + return -ENOTTY; } // File release handler diff --git a/kernel/supercalls.h b/kernel/supercalls.h index 7b4b14cb..01e180ff 100644 --- a/kernel/supercalls.h +++ b/kernel/supercalls.h @@ -18,6 +18,7 @@ struct ksu_become_daemon_cmd { struct ksu_get_info_cmd { __u32 version; // Output: KERNEL_SU_VERSION __u32 flags; // Output: flags (bit 0: MODULE mode) + __u32 features; // Output: max feature ID supported }; struct ksu_report_event_cmd { @@ -61,12 +62,15 @@ struct ksu_set_app_profile_cmd { struct app_profile profile; // Input: app profile structure }; -struct ksu_is_su_enabled_cmd { - __u8 enabled; // Output: true if su compat enabled +struct ksu_get_feature_cmd { + __u32 feature_id; // Input: feature ID (enum ksu_feature_id) + __u64 value; // Output: feature value/state + __u8 supported; // Output: true if feature is supported, false otherwise }; -struct ksu_enable_su_cmd { - __u8 enable; // Input: true to enable, false to disable +struct ksu_set_feature_cmd { + __u32 feature_id; // Input: feature ID (enum ksu_feature_id) + __u64 value; // Input: feature value/state to set }; // Other command structures @@ -109,8 +113,8 @@ struct ksu_enable_uid_scanner_cmd { #define KSU_IOCTL_GET_MANAGER_UID _IOR('K', 10, struct ksu_get_manager_uid_cmd) #define KSU_IOCTL_GET_APP_PROFILE _IOWR('K', 11, struct ksu_get_app_profile_cmd) #define KSU_IOCTL_SET_APP_PROFILE _IOW('K', 12, struct ksu_set_app_profile_cmd) -#define KSU_IOCTL_IS_SU_ENABLED _IOR('K', 13, struct ksu_is_su_enabled_cmd) -#define KSU_IOCTL_ENABLE_SU _IOW('K', 14, struct ksu_enable_su_cmd) +#define KSU_IOCTL_GET_FEATURE _IOWR('K', 13, struct ksu_get_feature_cmd) +#define KSU_IOCTL_SET_FEATURE _IOW('K', 14, struct ksu_set_feature_cmd) // Other IOCTL command definitions #define KSU_IOCTL_GET_FULL_VERSION _IOR('K', 100, struct ksu_get_full_version_cmd) #define KSU_IOCTL_HOOK_TYPE _IOR('K', 101, struct ksu_hook_type_cmd) @@ -123,18 +127,12 @@ struct ksu_enable_uid_scanner_cmd { typedef int (*ksu_ioctl_handler_t)(void __user *arg); typedef bool (*ksu_perm_check_t)(void); -// Permission check functions -bool perm_check_manager(void); -bool perm_check_root(void); -bool perm_check_basic(void); -bool perm_check_all(void); - // IOCTL command mapping struct ksu_ioctl_cmd_map { unsigned int cmd; + const char *name; ksu_ioctl_handler_t handler; ksu_perm_check_t perm_check; // Permission check function - const char *name; // Command name for logging }; // Install KSU fd to current process diff --git a/manager/app/src/main/cpp/jni.c b/manager/app/src/main/cpp/jni.c index a6f73373..ccae1402 100644 --- a/manager/app/src/main/cpp/jni.c +++ b/manager/app/src/main/cpp/jni.c @@ -5,6 +5,7 @@ #include #include #include +#include NativeBridgeNP(getVersion, jint) { uint32_t version = get_version(); @@ -300,6 +301,14 @@ NativeBridge(setSuEnabled, jboolean, jboolean enabled) { return set_su_enabled(enabled); } +NativeBridgeNP(isKernelUmountEnabled, jboolean) { + return is_kernel_umount_enabled(); +} + +NativeBridge(setKernelUmountEnabled, jboolean, jboolean enabled) { + return set_kernel_umount_enabled(enabled); +} + // Check if KPM is enabled NativeBridgeNP(isKPMEnabled, jboolean) { return is_KPM_enable(); diff --git a/manager/app/src/main/cpp/ksu.c b/manager/app/src/main/cpp/ksu.c index 27c1fe22..a1393a06 100644 --- a/manager/app/src/main/cpp/ksu.c +++ b/manager/app/src/main/cpp/ksu.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -133,13 +134,56 @@ int get_app_profile(struct app_profile *profile) { } bool set_su_enabled(bool enabled) { - struct ksu_enable_su_cmd cmd = {.enable = enabled}; - return ksuctl(KSU_IOCTL_ENABLE_SU, &cmd) == 0; + struct ksu_set_feature_cmd cmd = {}; + cmd.feature_id = KSU_FEATURE_SU_COMPAT; + cmd.value = enabled ? 1 : 0; + return ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0; } bool is_su_enabled() { - struct ksu_is_su_enabled_cmd cmd = {}; - return ksuctl(KSU_IOCTL_IS_SU_ENABLED, &cmd) == 0 && cmd.enabled; + struct ksu_get_feature_cmd cmd = {}; + cmd.feature_id = KSU_FEATURE_SU_COMPAT; + if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) != 0) { + return false; + } + if (!cmd.supported) { + return false; + } + return cmd.value != 0; +} + +static inline bool get_feature(uint32_t feature_id, uint64_t *out_value, bool *out_supported) { + struct ksu_get_feature_cmd cmd = {}; + cmd.feature_id = feature_id; + if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) != 0) { + return false; + } + if (out_value) *out_value = cmd.value; + if (out_supported) *out_supported = cmd.supported; + return true; +} + +static inline bool set_feature(uint32_t feature_id, uint64_t value) { + struct ksu_set_feature_cmd cmd = {}; + cmd.feature_id = feature_id; + cmd.value = value; + return ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0; +} + +bool set_kernel_umount_enabled(bool enabled) { + return set_feature(KSU_FEATURE_KERNEL_UMOUNT, enabled ? 1 : 0); +} + +bool is_kernel_umount_enabled() { + uint64_t value = 0; + bool supported = false; + if (!get_feature(KSU_FEATURE_KERNEL_UMOUNT, &value, &supported)) { + return false; + } + if (!supported) { + return false; + } + return value != 0; } void get_full_version(char* buff) { diff --git a/manager/app/src/main/cpp/ksu.h b/manager/app/src/main/cpp/ksu.h index 653dae00..f2674368 100644 --- a/manager/app/src/main/cpp/ksu.h +++ b/manager/app/src/main/cpp/ksu.h @@ -6,7 +6,7 @@ #define KERNELSU_KSU_H #include "prelude.h" -#include +#include #include #include @@ -105,10 +105,6 @@ bool set_app_profile(const struct app_profile* profile); int get_app_profile(struct app_profile* profile); -bool set_su_enabled(bool enabled); - -bool is_su_enabled(); - bool is_KPM_enable(); void get_hook_type(char* hook_type); @@ -129,50 +125,69 @@ bool set_uid_scanner_enabled(bool enabled); bool clear_uid_scanner_environment(); +// Feature IDs +enum ksu_feature_id { + KSU_FEATURE_SU_COMPAT = 0, + KSU_FEATURE_KERNEL_UMOUNT = 1, +}; + +// Generic feature API +struct ksu_get_feature_cmd { + uint32_t feature_id; // Input: feature ID + uint64_t value; // Output: feature value/state + uint8_t supported; // Output: whether the feature is supported +}; + +struct ksu_set_feature_cmd { + uint32_t feature_id; // Input: feature ID + uint64_t value; // Input: feature value/state to set +}; + struct ksu_become_daemon_cmd { - __u8 token[65]; // Input: daemon token (null-terminated) + uint8_t token[65]; // Input: daemon token (null-terminated) }; struct ksu_get_info_cmd { - __u32 version; // Output: KERNEL_SU_VERSION - __u32 flags; // Output: flags (bit 0: MODULE mode) + uint32_t version; // Output: KERNEL_SU_VERSION + uint32_t flags; // Output: flags (bit 0: MODULE mode) + uint32_t features; // Output: max feature ID supported (KSU_FEATURE_MAX) }; struct ksu_report_event_cmd { - __u32 event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc. + uint32_t event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc. }; struct ksu_set_sepolicy_cmd { - __u64 cmd; // Input: sepolicy command - __aligned_u64 arg; // Input: sepolicy argument pointer + uint64_t cmd; // Input: sepolicy command + uint64_t arg; // Input: sepolicy argument pointer }; struct ksu_check_safemode_cmd { - __u8 in_safe_mode; // Output: true if in safe mode, false otherwise + uint8_t in_safe_mode; // Output: true if in safe mode, false otherwise }; struct ksu_get_allow_list_cmd { - __u32 uids[128]; // Output: array of allowed/denied UIDs - __u32 count; // Output: number of UIDs in array - __u8 allow; // Input: true for allow list, false for deny list + uint32_t uids[128]; // Output: array of allowed/denied UIDs + uint32_t count; // Output: number of UIDs in array + uint8_t allow; // Input: true for allow list, false for deny list }; struct ksu_uid_granted_root_cmd { - __u32 uid; // Input: target UID to check - __u8 granted; // Output: true if granted, false otherwise + uint32_t uid; // Input: target UID to check + uint8_t granted; // Output: true if granted, false otherwise }; struct ksu_uid_should_umount_cmd { - __u32 uid; // Input: target UID to check - __u8 should_umount; // Output: true if should umount, false otherwise + uint32_t uid; // Input: target UID to check + uint8_t should_umount; // Output: true if should umount, false otherwise }; struct ksu_get_manager_uid_cmd { - __u32 uid; // Output: manager UID + uint32_t uid; // Output: manager UID }; struct ksu_set_manager_uid_cmd { - __u32 uid; // Input: new manager UID + uint32_t uid; // Input: new manager UID }; struct ksu_get_app_profile_cmd { @@ -183,13 +198,13 @@ struct ksu_set_app_profile_cmd { struct app_profile profile; // Input: app profile structure }; -struct ksu_is_su_enabled_cmd { - __u8 enabled; // Output: true if su compat enabled -}; +// Su compat +bool set_su_enabled(bool enabled); +bool is_su_enabled(); -struct ksu_enable_su_cmd { - __u8 enable; // Input: true to enable, false to disable -}; +// Kernel umount +bool set_kernel_umount_enabled(bool enabled); +bool is_kernel_umount_enabled(); // Other command structures struct ksu_get_full_version_cmd { @@ -201,7 +216,7 @@ struct ksu_hook_type_cmd { }; struct ksu_enable_kpm_cmd { - __u8 enabled; // Output: true if KPM is enabled + uint8_t enabled; // Output: true if KPM is enabled }; struct ksu_dynamic_manager_cmd { @@ -213,9 +228,9 @@ struct ksu_get_managers_cmd { }; struct ksu_enable_uid_scanner_cmd { - __u32 operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV) - __u32 enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE) - __u64 status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS) + uint32_t operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV) + uint32_t enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE) + uint64_t status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS) }; // IOCTL command definitions @@ -231,8 +246,8 @@ struct ksu_enable_uid_scanner_cmd { #define KSU_IOCTL_GET_MANAGER_UID _IOR('K', 10, struct ksu_get_manager_uid_cmd) #define KSU_IOCTL_GET_APP_PROFILE _IOWR('K', 11, struct ksu_get_app_profile_cmd) #define KSU_IOCTL_SET_APP_PROFILE _IOW('K', 12, struct ksu_set_app_profile_cmd) -#define KSU_IOCTL_IS_SU_ENABLED _IOR('K', 13, struct ksu_is_su_enabled_cmd) -#define KSU_IOCTL_ENABLE_SU _IOW('K', 14, struct ksu_enable_su_cmd) +#define KSU_IOCTL_GET_FEATURE _IOWR('K', 13, struct ksu_get_feature_cmd) +#define KSU_IOCTL_SET_FEATURE _IOW('K', 14, struct ksu_set_feature_cmd) // Other IOCTL command definitions #define KSU_IOCTL_GET_FULL_VERSION _IOR('K', 100, struct ksu_get_full_version_cmd) diff --git a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt index f58dc55f..72535ada 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt @@ -98,7 +98,16 @@ object Natives { */ external fun isSuEnabled(): Boolean external fun setSuEnabled(enabled: Boolean): Boolean - external fun grantRoot(): Boolean + + /** + * Kernel module umount can be disabled temporarily. + * 0: disabled + * 1: enabled + * negative : error + */ + external fun isKernelUmountEnabled(): Boolean + external fun setKernelUmountEnabled(enabled: Boolean): Boolean + external fun isKPMEnabled(): Boolean external fun getHookType(): String diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt index 877e7042..603a7aac 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.Input import androidx.compose.material.icons.filled.AutoFixHigh import androidx.compose.material.icons.filled.FileUpload import androidx.compose.material.icons.filled.Security @@ -205,12 +206,29 @@ fun InstallScreen( } } + val selectLkmLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == Activity.RESULT_OK) { + it.data?.data?.let { uri -> + lkmSelection = LkmSelection.LkmUri(uri) + } + } + } + + val onLkmUpload = { + selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/octet-stream" + }) + } + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) Scaffold( topBar = { TopBar( onBack = { navigator.popBackStack() }, + onLkmUpload = onLkmUpload, scrollBehavior = scrollBehavior ) }, @@ -934,6 +952,7 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle { @Composable private fun TopBar( onBack: () -> Unit = {}, + onLkmUpload: () -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior? = null ) { val colorScheme = MaterialTheme.colorScheme @@ -966,6 +985,17 @@ private fun TopBar( windowInsets = WindowInsets.safeDrawing.only( WindowInsetsSides.Top + WindowInsetsSides.Horizontal ), + actions = { + IconButton( + modifier = Modifier.padding(end = 16.dp), + onClick = onLkmUpload + ) { + Icon( + Icons.AutoMirrored.Filled.Input, + contentDescription = null + ) + } + }, scrollBehavior = scrollBehavior ) } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 6f138b34..e3020c9b 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.rounded.FolderDelete import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -165,6 +166,23 @@ fun SettingScreen(navigator: DestinationsNavigator) { } ) } + + var isKernelUmountDisabled by rememberSaveable { + mutableStateOf(!Natives.isKernelUmountEnabled()) + } + SwitchItem( + icon = Icons.Rounded.FolderDelete, + title = stringResource(id = R.string.settings_disable_kernel_umount), + summary = stringResource(id = R.string.settings_disable_kernel_umount_summary), + checked = isKernelUmountDisabled, + onCheckedChange = { checked: Boolean -> + val shouldEnable = !checked + if (Natives.setKernelUmountEnabled(shouldEnable)) { + isKernelUmountDisabled = !shouldEnable + } + } + ) + // 强制签名验证开关 var forceSignatureVerification by rememberSaveable { mutableStateOf(prefs.getBoolean("force_signature_verification", false)) diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index 6073ee6f..108901ca 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -164,6 +164,8 @@ 不允许授予 %s 超级用户权限 禁用 su 兼容性 临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响) + 关闭内核 umount + 临时关闭 KernelSU 控制的内核级 umount 行为。 确定要安装以下 %1$d 个模块吗?\n\n%2$s 更多设置 SELinux diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 2b2e455e..4664ff11 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -166,6 +166,8 @@ Granting superuser to %s is not allowed Disable su compatibility Temporarily disable any applications from obtaining root privileges via the ⁠su command (Existing root processes will not be affected) + Disable kernel umount + Temporarily disable kernel-level umount behavior controlled by KernelSU. Sure you want to install the following %1$d modules? \n\n%2$s More settings SELinux diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index 2e364044..eca619a6 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -64,6 +64,12 @@ enum Commands { command: Profile, }, + /// Manage kernel features + Feature { + #[command(subcommand)] + command: Feature, + }, + /// Patch boot or init_boot images to apply KernelSU BootPatch { /// boot image path, if not specified, will try to find the boot image automatically @@ -281,6 +287,32 @@ enum Profile { ListTemplates, } +#[derive(clap::Subcommand, Debug)] +enum Feature { + /// Get feature value and support status + Get { + /// Feature ID or name (su_compat, kernel_umount) + id: String, + }, + + /// Set feature value + Set { + /// Feature ID or name + id: String, + /// Feature value (0=disable, 1=enable) + value: u64, + }, + + /// List all available features + List, + + /// Load configuration from file and apply to kernel + Load, + + /// Save current kernel feature states to file + Save, +} + #[cfg(target_arch = "aarch64")] mod kpm_cmd { use clap::Subcommand; @@ -368,6 +400,14 @@ pub fn run() -> Result<()> { Profile::ListTemplates => crate::profile::list_templates(), }, + Commands::Feature { command } => match command { + Feature::Get { id } => crate::feature::get_feature(id), + Feature::Set { id, value } => crate::feature::set_feature(id, value), + Feature::List => crate::feature::list_features(), + Feature::Load => crate::feature::load_config_and_apply(), + Feature::Save => crate::feature::save_config(), + }, + Commands::Debug { command } => match command { Debug::SetManager { apk } => debug::set_manager(&apk), Debug::GetSign { apk } => { diff --git a/userspace/ksud/src/feature.rs b/userspace/ksud/src/feature.rs new file mode 100644 index 00000000..9cb395d4 --- /dev/null +++ b/userspace/ksud/src/feature.rs @@ -0,0 +1,282 @@ +use anyhow::{Context, Result, bail}; +use const_format::concatcp; +use std::collections::HashMap; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; + +use crate::defs; + +const FEATURE_CONFIG_PATH: &str = concatcp!(defs::WORKING_DIR, ".feature_config"); +const FEATURE_MAGIC: u32 = 0x7f4b5355; +const FEATURE_VERSION: u32 = 1; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum FeatureId { + SuCompat = 0, + KernelUmount = 1, +} + +impl FeatureId { + pub fn from_u32(id: u32) -> Option { + match id { + 0 => Some(FeatureId::SuCompat), + 1 => Some(FeatureId::KernelUmount), + _ => None, + } + } + + pub fn name(&self) -> &'static str { + match self { + FeatureId::SuCompat => "su_compat", + FeatureId::KernelUmount => "kernel_umount", + } + } + + pub fn description(&self) -> &'static str { + match self { + FeatureId::SuCompat => { + "SU Compatibility Mode - allows authorized apps to gain root via traditional 'su' command" + } + FeatureId::KernelUmount => { + "Kernel Umount - controls whether kernel automatically unmounts modules when not needed" + } + } + } +} + +fn parse_feature_id(name: &str) -> Result { + match name { + "su_compat" | "0" => Ok(FeatureId::SuCompat), + "kernel_umount" | "1" => Ok(FeatureId::KernelUmount), + _ => bail!("Unknown feature: {}", name), + } +} + +pub fn load_binary_config() -> Result> { + let path = Path::new(FEATURE_CONFIG_PATH); + if !path.exists() { + log::info!("Feature config not found, using defaults"); + return Ok(HashMap::new()); + } + + let mut file = File::open(path).with_context(|| "Failed to open feature config")?; + + let mut magic_buf = [0u8; 4]; + file.read_exact(&mut magic_buf) + .with_context(|| "Failed to read magic")?; + let magic = u32::from_le_bytes(magic_buf); + + if magic != FEATURE_MAGIC { + bail!( + "Invalid feature config magic: expected 0x{:08x}, got 0x{:08x}", + FEATURE_MAGIC, + magic + ); + } + + let mut version_buf = [0u8; 4]; + file.read_exact(&mut version_buf) + .with_context(|| "Failed to read version")?; + let version = u32::from_le_bytes(version_buf); + + if version != FEATURE_VERSION { + log::warn!( + "Feature config version mismatch: expected {}, got {}", + FEATURE_VERSION, + version + ); + } + + let mut count_buf = [0u8; 4]; + file.read_exact(&mut count_buf) + .with_context(|| "Failed to read count")?; + let count = u32::from_le_bytes(count_buf); + + let mut features = HashMap::new(); + for _ in 0..count { + let mut id_buf = [0u8; 4]; + let mut value_buf = [0u8; 8]; + + file.read_exact(&mut id_buf) + .with_context(|| "Failed to read feature id")?; + file.read_exact(&mut value_buf) + .with_context(|| "Failed to read feature value")?; + + let id = u32::from_le_bytes(id_buf); + let value = u64::from_le_bytes(value_buf); + + features.insert(id, value); + } + + log::info!("Loaded {} features from config", features.len()); + Ok(features) +} + +pub fn save_binary_config(features: &HashMap) -> Result<()> { + crate::utils::ensure_dir_exists(Path::new(defs::WORKING_DIR))?; + + let path = Path::new(FEATURE_CONFIG_PATH); + let mut file = File::create(path).with_context(|| "Failed to create feature config")?; + + file.write_all(&FEATURE_MAGIC.to_le_bytes()) + .with_context(|| "Failed to write magic")?; + + file.write_all(&FEATURE_VERSION.to_le_bytes()) + .with_context(|| "Failed to write version")?; + + let count = features.len() as u32; + file.write_all(&count.to_le_bytes()) + .with_context(|| "Failed to write count")?; + + for (&id, &value) in features.iter() { + file.write_all(&id.to_le_bytes()) + .with_context(|| format!("Failed to write feature id {}", id))?; + file.write_all(&value.to_le_bytes()) + .with_context(|| format!("Failed to write feature value for id {}", id))?; + } + + file.sync_all() + .with_context(|| "Failed to sync feature config")?; + + log::info!("Saved {} features to config", features.len()); + Ok(()) +} + +pub fn apply_config(features: &HashMap) -> Result<()> { + log::info!("Applying feature configuration to kernel..."); + + let mut applied = 0; + for (&id, &value) in features.iter() { + match crate::ksucalls::set_feature(id, value) { + Ok(_) => { + if let Some(feature_id) = FeatureId::from_u32(id) { + log::info!("Set feature {} to {}", feature_id.name(), value); + } else { + log::info!("Set feature {} to {}", id, value); + } + applied += 1; + } + Err(e) => { + log::warn!("Failed to set feature {}: {}", id, e); + } + } + } + + log::info!("Applied {} features successfully", applied); + Ok(()) +} + +pub fn get_feature(id: String) -> Result<()> { + let feature_id = parse_feature_id(&id)?; + let (value, supported) = crate::ksucalls::get_feature(feature_id as u32) + .with_context(|| format!("Failed to get feature {}", id))?; + + if !supported { + println!("Feature '{}' is not supported by kernel", id); + return Ok(()); + } + + println!("Feature: {} ({})", feature_id.name(), feature_id as u32); + println!("Description: {}", feature_id.description()); + println!("Value: {}", value); + println!( + "Status: {}", + if value != 0 { "enabled" } else { "disabled" } + ); + + Ok(()) +} + +pub fn set_feature(id: String, value: u64) -> Result<()> { + let feature_id = parse_feature_id(&id)?; + + crate::ksucalls::set_feature(feature_id as u32, value) + .with_context(|| format!("Failed to set feature {} to {}", id, value))?; + + println!( + "Feature '{}' set to {} ({})", + feature_id.name(), + value, + if value != 0 { "enabled" } else { "disabled" } + ); + + Ok(()) +} + +pub fn list_features() -> Result<()> { + println!("Available Features:"); + println!("{}", "=".repeat(80)); + + let all_features = [FeatureId::SuCompat, FeatureId::KernelUmount]; + + for feature_id in all_features.iter() { + let id = *feature_id as u32; + let (value, supported) = crate::ksucalls::get_feature(id).unwrap_or((0, false)); + + let status = if !supported { + "NOT_SUPPORTED".to_string() + } else if value != 0 { + format!("ENABLED ({})", value) + } else { + "DISABLED".to_string() + }; + + println!("[{}] {} (ID={})", status, feature_id.name(), id); + println!(" {}", feature_id.description()); + println!(); + } + + Ok(()) +} + +pub fn load_config_and_apply() -> Result<()> { + let features = load_binary_config()?; + + if features.is_empty() { + println!("No features found in config file"); + return Ok(()); + } + + apply_config(&features)?; + println!("Feature configuration loaded and applied"); + Ok(()) +} + +pub fn save_config() -> Result<()> { + let mut features = HashMap::new(); + + let all_features = [FeatureId::SuCompat, FeatureId::KernelUmount]; + + for feature_id in all_features.iter() { + let id = *feature_id as u32; + if let Ok((value, supported)) = crate::ksucalls::get_feature(id) + && supported { + features.insert(id, value); + log::info!("Saved feature {} = {}", feature_id.name(), value); + } + } + + save_binary_config(&features)?; + println!( + "Current feature states saved to config file ({} features)", + features.len() + ); + Ok(()) +} + +pub fn init_features() -> Result<()> { + log::info!("Initializing features from config..."); + + let features = load_binary_config()?; + + if features.is_empty() { + log::info!("No feature config found, skipping initialization"); + return Ok(()); + } + + apply_config(&features)?; + + Ok(()) +} diff --git a/userspace/ksud/src/init_event.rs b/userspace/ksud/src/init_event.rs index 096c9b28..d1ea7491 100644 --- a/userspace/ksud/src/init_event.rs +++ b/userspace/ksud/src/init_event.rs @@ -87,6 +87,11 @@ pub fn on_post_data_fs() -> Result<()> { warn!("apply root profile sepolicy failed: {e}"); } + // load feature config + if let Err(e) = crate::feature::init_features() { + warn!("init features failed: {e}"); + } + #[cfg(target_arch = "aarch64")] if let Err(e) = kpm::start_kpm_watcher() { warn!("KPM: Failed to start KPM watcher: {e}"); diff --git a/userspace/ksud/src/ksucalls.rs b/userspace/ksud/src/ksucalls.rs index eae40020..5884bbc5 100644 --- a/userspace/ksud/src/ksucalls.rs +++ b/userspace/ksud/src/ksucalls.rs @@ -8,11 +8,13 @@ const EVENT_POST_FS_DATA: u32 = 1; const EVENT_BOOT_COMPLETED: u32 = 2; const EVENT_MODULE_MOUNTED: u32 = 3; -const KSU_IOCTL_GRANT_ROOT: u32 = 0x4B01; // _IO('K', 1) +const KSU_IOCTL_GRANT_ROOT: u32 = 0x00004b01; // _IO('K', 1) const KSU_IOCTL_GET_INFO: u32 = 0x80084b02; // _IOR('K', 2, struct ksu_get_info_cmd) -const KSU_IOCTL_REPORT_EVENT: u32 = 0x40044b03; // _IOW('K', 3, struct ksu_report_event_cmd) +const KSU_IOCTL_REPORT_EVENT: u32 = 0x40084b03; // _IOW('K', 3, struct ksu_report_event_cmd) const KSU_IOCTL_SET_SEPOLICY: u32 = 0xc0104b04; // _IOWR('K', 4, struct ksu_set_sepolicy_cmd) const KSU_IOCTL_CHECK_SAFEMODE: u32 = 0x80014b05; // _IOR('K', 5, struct ksu_check_safemode_cmd) +const KSU_IOCTL_GET_FEATURE: u32 = 0xc00c4b0d; // _IOWR('K', 13, struct ksu_get_feature_cmd) +const KSU_IOCTL_SET_FEATURE: u32 = 0x40084b0e; // _IOW('K', 14, struct ksu_set_feature_cmd) #[repr(C)] #[derive(Clone, Copy, Default)] @@ -39,6 +41,21 @@ struct CheckSafemodeCmd { in_safe_mode: u8, } +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct GetFeatureCmd { + feature_id: u32, + value: u64, + supported: u8, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct SetFeatureCmd { + feature_id: u32, + value: u64, +} + // Global driver fd cache #[cfg(any(target_os = "linux", target_os = "android"))] static DRIVER_FD: OnceLock = OnceLock::new(); @@ -183,4 +200,23 @@ pub fn set_sepolicy(cmd: &SetSepolicyCmd) -> std::io::Result<()> { let mut ioctl_cmd = *cmd; ksuctl(KSU_IOCTL_SET_SEPOLICY, &mut ioctl_cmd as *mut _)?; Ok(()) -} \ No newline at end of file +} + +/// Get feature value and support status from kernel +/// Returns (value, supported) +pub fn get_feature(feature_id: u32) -> std::io::Result<(u64, bool)> { + let mut cmd = GetFeatureCmd { + feature_id, + value: 0, + supported: 0, + }; + ksuctl(KSU_IOCTL_GET_FEATURE, &mut cmd as *mut _)?; + Ok((cmd.value, cmd.supported != 0)) +} + +/// Set feature value in kernel +pub fn set_feature(feature_id: u32, value: u64) -> std::io::Result<()> { + let mut cmd = SetFeatureCmd { feature_id, value }; + ksuctl(KSU_IOCTL_SET_FEATURE, &mut cmd as *mut _)?; + Ok(()) +} diff --git a/userspace/ksud/src/main.rs b/userspace/ksud/src/main.rs index 55aaf2a3..3ff3af02 100644 --- a/userspace/ksud/src/main.rs +++ b/userspace/ksud/src/main.rs @@ -4,6 +4,7 @@ mod boot_patch; mod cli; mod debug; mod defs; +mod feature; mod init_event; #[cfg(target_arch = "aarch64")] mod kpm;