From d86b43915539996fac2ef744d2be8c32ffc0ec47 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 22 May 2024 18:48:06 -0700 Subject: [PATCH] Initial release of the sudo source (#71) Initial release of the sudo source --------- Co-authored-by: Mike Griese --- .cargo/config.toml | 16 + .cargo/ms-toolchain-config.toml | 25 + .pipelines/OneBranch.Buddy.yml | 68 ++ .pipelines/OneBranch.Common.yml | 103 +++ .pipelines/PR.Pipeline.yml | 112 +++ .pipelines/Standard.Pipeline.yml | 179 ++++ .pipelines/convert-resx-to-rc.ps1 | 354 ++++++++ .pipelines/daily-loc-build.yml | 49 ++ .../steps-fetch-and-prepare-localizations.yml | 23 + Building.md | 75 ++ CONTRIBUTING.md | 52 +- Cargo.lock | 516 ++++++++++++ Cargo.toml | 45 ++ cpp/logging/EventViewerLogging.c | 7 + cpp/logging/README.md | 28 + cpp/logging/instrumentation.man | 93 +++ cpp/rpc/README.md | 17 + cpp/rpc/RpcClient.c | 82 ++ cpp/rpc/sudo_rpc.acf | 5 + cpp/rpc/sudo_rpc.idl | 30 + docs/generate.bat | 28 + docs/obsidian-callouts.lua | 17 + docs/settings-app-toggle.png | Bin 0 -> 30624 bytes docs/sudo-conhost.png | Bin 0 -> 22098 bytes docs/sudo-terminal-multi.png | Bin 0 -> 45249 bytes docs/sudo-terminal-redirected.png | Bin 0 -> 40813 bytes docs/sudo-terminal-service.png | Bin 0 -> 56982 bytes docs/sudo-terminal-single.png | Bin 0 -> 38418 bytes docs/sudo.png | Bin 0 -> 9908 bytes docs/versions.md | 22 + enable_sudo.cmd | 19 + sudo/Cargo.toml | 83 ++ sudo/Resources/en-US/Sudo.resw | 273 +++++++ sudo/build.rs | 280 +++++++ sudo/src/elevate_handler.rs | 167 ++++ sudo/src/helpers.rs | 762 ++++++++++++++++++ sudo/src/logging_bindings.rs | 146 ++++ sudo/src/main.rs | 461 +++++++++++ sudo/src/messages.rs | 13 + sudo/src/r.rs | 23 + sudo/src/rpc_bindings.rs | 34 + sudo/src/rpc_bindings_client.rs | 109 +++ sudo/src/rpc_bindings_server.rs | 295 +++++++ sudo/src/run_handler.rs | 549 +++++++++++++ sudo/src/tests/mod.rs | 26 + sudo/src/tracing.rs | 52 ++ sudo/sudo.manifest | 9 + sudo/sudo.rc | 24 + sudo_events/Cargo.toml | 8 + sudo_events/build.rs | 31 + sudo_events/src/events_template.rs | 47 ++ sudo_events/src/lib.rs | 1 + tools/gen-lang-codes.ps1 | 153 ++++ tools/sudo.tvpp | 26 + tools/tests.ipynb | 603 ++++++++++++++ win32resources/Cargo.toml | 4 + win32resources/src/lib.rs | 79 ++ 57 files changed, 6217 insertions(+), 6 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .cargo/ms-toolchain-config.toml create mode 100644 .pipelines/OneBranch.Buddy.yml create mode 100644 .pipelines/OneBranch.Common.yml create mode 100644 .pipelines/PR.Pipeline.yml create mode 100644 .pipelines/Standard.Pipeline.yml create mode 100644 .pipelines/convert-resx-to-rc.ps1 create mode 100644 .pipelines/daily-loc-build.yml create mode 100644 .pipelines/steps-fetch-and-prepare-localizations.yml create mode 100644 Building.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 cpp/logging/EventViewerLogging.c create mode 100644 cpp/logging/README.md create mode 100644 cpp/logging/instrumentation.man create mode 100644 cpp/rpc/README.md create mode 100644 cpp/rpc/RpcClient.c create mode 100644 cpp/rpc/sudo_rpc.acf create mode 100644 cpp/rpc/sudo_rpc.idl create mode 100644 docs/generate.bat create mode 100644 docs/obsidian-callouts.lua create mode 100644 docs/settings-app-toggle.png create mode 100644 docs/sudo-conhost.png create mode 100644 docs/sudo-terminal-multi.png create mode 100644 docs/sudo-terminal-redirected.png create mode 100644 docs/sudo-terminal-service.png create mode 100644 docs/sudo-terminal-single.png create mode 100644 docs/sudo.png create mode 100644 docs/versions.md create mode 100644 enable_sudo.cmd create mode 100644 sudo/Cargo.toml create mode 100644 sudo/Resources/en-US/Sudo.resw create mode 100644 sudo/build.rs create mode 100644 sudo/src/elevate_handler.rs create mode 100644 sudo/src/helpers.rs create mode 100644 sudo/src/logging_bindings.rs create mode 100644 sudo/src/main.rs create mode 100644 sudo/src/messages.rs create mode 100644 sudo/src/r.rs create mode 100644 sudo/src/rpc_bindings.rs create mode 100644 sudo/src/rpc_bindings_client.rs create mode 100644 sudo/src/rpc_bindings_server.rs create mode 100644 sudo/src/run_handler.rs create mode 100644 sudo/src/tests/mod.rs create mode 100644 sudo/src/tracing.rs create mode 100644 sudo/sudo.manifest create mode 100644 sudo/sudo.rc create mode 100644 sudo_events/Cargo.toml create mode 100644 sudo_events/build.rs create mode 100644 sudo_events/src/events_template.rs create mode 100644 sudo_events/src/lib.rs create mode 100644 tools/gen-lang-codes.ps1 create mode 100644 tools/sudo.tvpp create mode 100644 tools/tests.ipynb create mode 100644 win32resources/Cargo.toml create mode 100644 win32resources/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..f222047 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,16 @@ +# -Ccontrol-flow-guard: Enable Control Flow Guard, needed for OneBranch's post-build analysis (https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-control-flow-guard). +[target.'cfg(target_os = "windows")'] +rustflags = [ + "-Ccontrol-flow-guard", + "-Ctarget-feature=+crt-static", + "-Clink-args=/DEFAULTLIB:ucrt.lib /NODEFAULTLIB:vcruntime.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libucrt.lib" +] + +# This fixes the following linker error on x86: +# error LNK2019: unresolved external symbol _NdrClientCall4 referenced in function ... +[target.'cfg(all(target_os = "windows", target_arch = "x86"))'] +rustflags = ["-Clink-args=/DEFAULTLIB:rpcrt4.lib"] + +# -Clink-args=/DYNAMICBASE /CETCOMPAT: Enable "shadow stack" (https://learn.microsoft.com/en-us/cpp/build/reference/cetcompat) +[target.'cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))'] +rustflags = ["-Clink-args=/DYNAMICBASE /CETCOMPAT"] diff --git a/.cargo/ms-toolchain-config.toml b/.cargo/ms-toolchain-config.toml new file mode 100644 index 0000000..e4a24ca --- /dev/null +++ b/.cargo/ms-toolchain-config.toml @@ -0,0 +1,25 @@ +# -Cehcont_guard: Enable EH Continuation Metadata (https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-eh-continuation-metadata). +# -Ccontrol-flow-guard: Enable Control Flow Guard, needed for OneBranch's post-build analysis (https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-control-flow-guard). +[target.'cfg(target_os = "windows")'] +rustflags = [ + "-Cehcont_guard", + "-Ccontrol-flow-guard", + "-Ctarget-feature=+crt-static", + "-Clink-args=/DEFAULTLIB:ucrt.lib /NODEFAULTLIB:vcruntime.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libucrt.lib" +] + +# This fixes the following linker error on x86: +# error LNK2019: unresolved external symbol _NdrClientCall4 referenced in function ... +[target.'cfg(all(target_os = "windows", target_arch = "x86"))'] +rustflags = ["-Clink-args=/DEFAULTLIB:rpcrt4.lib"] + +# -Clink-args=/DYNAMICBASE /CETCOMPAT: Enable "shadow stack" (https://learn.microsoft.com/en-us/cpp/build/reference/cetcompat) +[target.'cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))'] +rustflags = ["-Clink-args=/DYNAMICBASE /CETCOMPAT"] + +# Setup the ADO Artifacts feed as a Registry: you'll need to use your own feed in your project that upstreams from crates.io. +# For more details see https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/azure-artifacts/cargo +[registries] +sudo_public_cargo = { index = "sparse+https://pkgs.dev.azure.com/microsoft/Dart/_packaging/sudo_public_cargo/Cargo/index/" } +[source.crates-io] +replace-with = "sudo_public_cargo" diff --git a/.pipelines/OneBranch.Buddy.yml b/.pipelines/OneBranch.Buddy.yml new file mode 100644 index 0000000..1a959db --- /dev/null +++ b/.pipelines/OneBranch.Buddy.yml @@ -0,0 +1,68 @@ +################################################################################# +# OneBranch Pipelines - Buddy # +# This pipeline was created by EasyStart from a sample located at: # +# https://aka.ms/obpipelines/easystart/samples # +# Documentation: https://aka.ms/obpipelines # +# Yaml Schema: https://aka.ms/obpipelines/yaml/schema # +# Retail Tasks: https://aka.ms/obpipelines/tasks # +# Support: https://aka.ms/onebranchsup # +################################################################################# + +trigger: +- master + +# Hourly builds: you may want to change these to nightly ("0 3 * * *" - run at 3am). +schedules: +- cron: 0 * * * * + displayName: Hourly build + branches: + include: + - master + always: true + +variables: + CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning + LinuxContainerImage: 'mcr.microsoft.com/onebranch/cbl-mariner/build:2.0' # Docker image which is used to build the project https://aka.ms/obpipelines/containers + WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + DEBIAN_FRONTEND: noninteractive + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@templates # https://aka.ms/obpipelines/templates + parameters: + cloudvault: # https://aka.ms/obpipelines/cloudvault + enabled: false + globalSdl: # https://aka.ms/obpipelines/sdl + binskim: + # Rust build scripts will not be built with spectre-mitigations enabled, so only scan the actual output binaries. + scanOutputDirectoryOnly: true + stages: + - stage: Build + jobs: + - job: Linux + pool: + type: linux + variables: + ob_outputDirectory: '$(Build.SourcesDirectory)/out' # More settings at https://aka.ms/obpipelines/yaml/jobs + additionalRustTargets: x86_64-unknown-linux-musl + # Cargo's default target dir is $(Build.SourcesDirectory)/target but $(Build.BinariesDirectory)/target is more appropriate. + cargo_target_dir: $(Build.BinariesDirectory)/target + steps: + - template: .pipelines/OneBranch.Common.yml@self + + - job: Windows + pool: + type: windows + variables: + ob_outputDirectory: '$(Build.SourcesDirectory)/out' # More settings at https://aka.ms/obpipelines/yaml/jobs + additionalRustTargets: i686-pc-windows-msvc + # For details on this cargo_target_dir setting, see https://eng.ms/docs/more/rust/topics/onebranch-workaround + cargo_target_dir: C:\cargo_target_dir + steps: + - template: .pipelines/OneBranch.Common.yml@self diff --git a/.pipelines/OneBranch.Common.yml b/.pipelines/OneBranch.Common.yml new file mode 100644 index 0000000..57fb6fb --- /dev/null +++ b/.pipelines/OneBranch.Common.yml @@ -0,0 +1,103 @@ +# Core build logic that is common to all OneBranch builds. +parameters: # parameters are shown up in ADO UI in a build queue time +- name: buildPlatforms + type: object + default: + - x86_64-pc-windows-msvc # x64 + - i686-pc-windows-msvc # x86 + - aarch64-pc-windows-msvc # arm64 + # - # arm32? + +- name: brandings + type: object + default: + - Inbox + - Stable + - Dev + +- name: tracingGuid + type: string + default: ffffffff-ffff-ffff-ffff-ffffffffffff + +steps: +- task: RustInstaller@1 + inputs: + # Can be any "MSRustup" version, such as ms-stable, ms-1.54 or ms-stable-20210513.5 - for more details see https://mscodehub.visualstudio.com/Rust/_git/rust.msrustup + # For supported versions see https://mscodehub.visualstudio.com/Rust/_packaging?_a=package&feed=Rust&view=versions&package=rust.tools-x86_64-pc-windows-msvc&protocolType=NuGet + # We recommend setting this to a specific numbered version such as `ms-1.68` -- we do not recommend + # setting it to `ms-stable`. + # For details on this, see https://eng.ms/docs/more/rust/topics/conventions#toolchain-usage + rustVersion: ms-1.75 + + # Space separated list of additional targets: only the host target is supported with the toolchain by default. + # + # This doesn't actually control what gets built - only which toolchains get installed on the build agent. + # + # Theoretically, I could somehow replace this with the buildPlatforms passed in as a parameter + additionalTargets: i686-pc-windows-msvc aarch64-pc-windows-msvc x86_64-pc-windows-msvc + + # URL of an Azure Artifacts feed configured with a crates.io upstream. Must be within the current ADO collection. + # NOTE: Azure Artifacts support for Rust is not yet public, but it is enabled for internal ADO organizations. + # https://learn.microsoft.com/en-us/azure/devops/artifacts/how-to/set-up-upstream-sources?view=azure-devops + cratesIoFeedOverride: sparse+https://pkgs.dev.azure.com/microsoft/Dart/_packaging/sudo_public_cargo/Cargo/index/ + + # URL of an Azure Artifacts NuGet feed configured with the mscodehub Rust feed as an upstream. + # * The feed must be within the current ADO collection. + # * The CI account, usually "Project Collection Build Service (org-name)", must have at least "Collaborator" permission. + # When setting up the upstream NuGet feed, use following Azure Artifacts feed locator: + # azure-feed://mscodehub/Rust/Rust@Release + toolchainFeed: https://pkgs.dev.azure.com/microsoft/_packaging/RustTools/nuget/v3/index.json + + displayName: Install Rust toolchain + +# We recommend making a separate `cargo fetch` step, as some build systems +# perform fetching entirely prior to the build, and perform the build with the +# network disabled. +- script: cargo fetch + displayName: Fetch crates + +# First, build and test each branding. +- ${{ each brand in parameters.brandings }}: + - script: cargo build --config .cargo\ms-toolchain-config.toml --no-default-features --features ${{brand}} --frozen 2>&1 + displayName: Build ${{brand}} Debug + + - script: cargo test --config .cargo\ms-toolchain-config.toml --no-default-features --features ${{brand}} --frozen 2>&1 + displayName: Test ${{brand}} Debug + +# We suggest fmt and clippy after build and test, to catch build breaks and test +# failures as quickly as possible. +# This only needs to happen once, not per-branding. +- script: cargo fmt --check 2>&1 + displayName: Check formatting + +- ${{ each brand in parameters.brandings }}: + - script: cargo clippy --config .cargo\ms-toolchain-config.toml --no-default-features --features ${{brand}} --frozen -- -D warnings 2>&1 + displayName: Clippy (Linting) ${{brand}} + + # Build release last because it takes the longest and we should have gotten + # all available error signal by this point. + - ${{ each platform in parameters.buildPlatforms }}: + - script: cargo build --config .cargo\ms-toolchain-config.toml --no-default-features --features ${{brand}} --target ${{platform}} --frozen --release 2>&1 + env: + MAGIC_TRACING_GUID: ${{ parameters.tracingGuid }} + displayName: Build ${{brand}}-${{platform}} Release + + # At this point, we've completed the build, but each of the outputs is in a + # subdir specific to the architecture being built. That's okay, but the artifact + # drop won't know to look for them in there. + # + # Copy them on out. + - task: CopyFiles@2 + displayName: Copy files to output (${{brand}}/${{platform}}) + inputs: + sourceFolder: '$(CARGO_TARGET_DIR)/${{platform}}/release' + targetFolder: '$(ob_outputDirectory)/${{brand}}/${{platform}}' + contents: '*' + +# only do this once +- task: CopyFiles@2 + displayName: Copy instrumentation manifest to output + inputs: + sourceFolder: 'cpp/logging' + targetFolder: '$(ob_outputDirectory)/' + contents: 'instrumentation.man' diff --git a/.pipelines/PR.Pipeline.yml b/.pipelines/PR.Pipeline.yml new file mode 100644 index 0000000..caa3c90 --- /dev/null +++ b/.pipelines/PR.Pipeline.yml @@ -0,0 +1,112 @@ +################################################################################# +# OneBranch Pipelines - Buddy # +# This pipeline was created by EasyStart from a sample located at: # +# https://aka.ms/obpipelines/easystart/samples # +# Documentation: https://aka.ms/obpipelines # +# Yaml Schema: https://aka.ms/obpipelines/yaml/schema # +# Retail Tasks: https://aka.ms/obpipelines/tasks # +# Support: https://aka.ms/onebranchsup # +################################################################################# + +trigger: none +# - main + +parameters: # parameters are shown up in ADO UI in a build queue time +# buildPlatforms: This controls which Rust triples we build sudo for. +# These three defaults correspond to x64, x86 and ARM64. +# They're used by: +# - The OneBranch.Common.yml, to control which --target's we build in release +# - The vpack task, below. +- name: buildPlatforms + type: object + default: + - x86_64-pc-windows-msvc # x64 + - i686-pc-windows-msvc # x86 + - aarch64-pc-windows-msvc # arm64 + +# Hourly builds: you may want to change these to nightly ("0 3 * * *" - run at 3am). +schedules: +- cron: 0 * * * * + displayName: Hourly build + branches: + include: + - master + always: true + +variables: + CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning + WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + + # LOAD BEARING - the vpack task fails without these + ROOT: $(Build.SourcesDirectory) + REPOROOT: $(Build.SourcesDirectory) + OUTPUTROOT: $(REPOROOT)\out + NUGET_XMLDOC_MODE: none + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + # We're an official build, so we need to extend from the _Official_ build template. + template: v2/Microsoft.NonOfficial.yml@templates + parameters: + platform: + name: 'windows_undocked' + product: 'sudo' + cloudvault: # https://aka.ms/obpipelines/cloudvault + enabled: false + globalSdl: # https://aka.ms/obpipelines/sdl + binskim: + # Rust build scripts will not be built with spectre-mitigations enabled, + # so only scan the actual output binaries. + scanOutputDirectoryOnly: true + stages: + # Our Build stage will build all three targets in one job, so we don't need + # to repeat most of the boilerplate work in three separate jobs. + - stage: Build + jobs: + - job: Windows + pool: + type: windows + + variables: + # Binaries will go here + ob_outputDirectory: '$(Build.SourcesDirectory)/out' # More settings at https://aka.ms/obpipelines/yaml/jobs + additionalTargets: $(parameters.buildPlatforms) + # For details on this cargo_target_dir setting, see https://eng.ms/docs/more/rust/topics/onebranch-workaround + cargo_target_dir: C:\cargo_target_dir + + # The "standard" pipeline has a bunch of other variables it sets here + # to control vpack creation. However, for a PR build, we don't really + # need any of that. + + steps: + # The actual build is over in Onebranch.Common.yml + - template: .pipelines/OneBranch.Common.yml@self + parameters: + buildPlatforms: ${{ parameters.buildPlatforms }} + # branding will use the default, which is set to build all of them. + # tracingGuid will use the default placeholder for PR builds + + # This is very shamelessly stolen from curl's pipeline. Small + # modification: since our cargo.toml has a bunch of lines with "version", + # bail after the first. + - script: |- + rem Parse the version out of cargo.toml + for /f "tokens=3 delims=- " %%x in ('findstr /c:"version = " sudo\cargo.toml') do (@echo ##vso[task.setvariable variable=SudoVersion]%%~x & goto :EOF) + displayName: 'Set SudoVersion' + + # Codesigning. Cribbed directly from the curl codesign task + - task: onebranch.pipeline.signing@1 + displayName: 'Sign files' + inputs: + command: 'sign' + signing_profile: 'external_distribution' + files_to_sign: '**/*.exe' + search_root: '$(ob_outputDirectory)' + use_testsign: false + in_container: true diff --git a/.pipelines/Standard.Pipeline.yml b/.pipelines/Standard.Pipeline.yml new file mode 100644 index 0000000..0375482 --- /dev/null +++ b/.pipelines/Standard.Pipeline.yml @@ -0,0 +1,179 @@ +################################################################################# +# OneBranch Pipelines - Buddy # +# This pipeline was created by EasyStart from a sample located at: # +# https://aka.ms/obpipelines/easystart/samples # +# Documentation: https://aka.ms/obpipelines # +# Yaml Schema: https://aka.ms/obpipelines/yaml/schema # +# Retail Tasks: https://aka.ms/obpipelines/tasks # +# Support: https://aka.ms/onebranchsup # +################################################################################# + +trigger: none +# - main + +parameters: # parameters are shown up in ADO UI in a build queue time +# buildPlatforms: This controls which Rust triples we build sudo for. +# These three defaults correspond to x64, x86 and ARM64. +# They're used by: +# - The OneBranch.Common.yml, to control which --target's we build in release +# - The vpack task, below. +- name: buildPlatforms + type: object + default: + - x86_64-pc-windows-msvc # x64 + - i686-pc-windows-msvc # x86 + - aarch64-pc-windows-msvc # arm64 + +# The official builds default to just the Inbox builds +- name: brandings + type: object + default: + - Inbox + # - Stable + # - Dev + +# Hourly builds: you may want to change these to nightly ("0 3 * * *" - run at 3am). +schedules: +- cron: 0 * * * * + displayName: Hourly build + branches: + include: + - master + always: true + +variables: + CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning + WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + + # LOAD BEARING - the vpack task fails without these + ROOT: $(Build.SourcesDirectory) + REPOROOT: $(Build.SourcesDirectory) + OUTPUTROOT: $(REPOROOT)\out + NUGET_XMLDOC_MODE: none + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + # We're an official build, so we need to extend from the _Official_ build template. + template: v2/Microsoft.Official.yml@templates + parameters: + platform: + name: 'windows_undocked' + product: 'sudo' + cloudvault: # https://aka.ms/obpipelines/cloudvault + enabled: false + globalSdl: # https://aka.ms/obpipelines/sdl + binskim: + # Rust build scripts will not be built with spectre-mitigations enabled, + # so only scan the actual output binaries. + scanOutputDirectoryOnly: true + stages: + # Our Build stage will build all three targets in one job, so we don't need + # to repeat most of the boilerplate work in three separate jobs. + - stage: Build + jobs: + - job: Windows + pool: + type: windows + + variables: + # Binaries will go here + ob_outputDirectory: '$(Build.SourcesDirectory)/out' # More settings at https://aka.ms/obpipelines/yaml/jobs + + # The vPack gets created from stuff in here + ob_createvpack_vpackdirectory: '$(ob_outputDirectory)/vpack' + # It will have a structure like: + # .../vpack/ + # - amd64/ + # - sudo.exe + # - i386/ + # - sudo.exe + # - arm64/ + # - sudo.exe + + # not sure where this goes + # additionalRustTargets: i686-pc-windows-msvc + additionalTargets: $(parameters.buildPlatforms) + + # For details on this cargo_target_dir setting, see https://eng.ms/docs/more/rust/topics/onebranch-workaround + cargo_target_dir: C:\cargo_target_dir + + # All these? Also variables that control the vpack creation. + ob_createvpack_enabled: true + ob_createvpack_packagename: 'windows_sudo.$(Build.SourceBranchName)' + ob_createvpack_owneralias: 'migrie@microsoft.com' + ob_createvpack_description: 'Sudo for Windows' + ob_createvpack_targetDestinationDirectory: '$(Destination)' + ob_createvpack_propsFile: false + ob_createvpack_provData: true + ob_createvpack_versionAs: string + ob_createvpack_version: '$(SudoVersion)-$(CDP_DEFINITION_BUILD_COUNT)' + ob_createvpack_metadata: '$(Build.SourceVersion)' + ob_createvpack_topLevelRetries: 0 + ob_createvpack_failOnStdErr: true + ob_createvpack_taskLogVerbosity: Detailed + ob_createvpack_verbose: true + + steps: + # Before we build! Right before we build, pull down localizations from + # Touchdown. + # + # The Terminal build would literally pass this as a parameter of a list + # of steps to the job-build-project.yml, which is overkill for us + - template: .pipelines/steps-fetch-and-prepare-localizations.yml@self + parameters: + includePseudoLoc: true + + # The actual build is over in Onebranch.Common.yml + - template: .pipelines/OneBranch.Common.yml@self + parameters: + buildPlatforms: ${{ parameters.buildPlatforms }} + brandings: ${{ parameters.brandings }} + tracingGuid: $(SecretTracingGuid) + + # This is very shamelessly stolen from curl's pipeline. Small + # modification: since our cargo.toml has a bunch of lines with "version", + # bail after the first. + - script: |- + rem Parse the version out of cargo.toml + for /f "tokens=3 delims=- " %%x in ('findstr /c:"version = " sudo\cargo.toml') do (@echo ##vso[task.setvariable variable=SudoVersion]%%~x & goto :EOF) + displayName: 'Set SudoVersion' + + # Codesigning. Cribbed directly from the curl codesign task + - task: onebranch.pipeline.signing@1 + displayName: 'Sign files' + inputs: + command: 'sign' + signing_profile: 'external_distribution' + files_to_sign: '**/*.exe' + search_root: '$(ob_outputDirectory)' + use_testsign: false + in_container: true + + # This stage grabs each of the sudo's we've built for different + # architectures, and copies them into the appropriate vpack directory. + - ${{ each platform in parameters.buildPlatforms }}: + - task: CopyFiles@2 + displayName: Copy files to vpack (${{platform}}) + inputs: + # We always use the 'Inbox' branding vvvvv here - vpacks are only ever consumed by the OS + sourceFolder: '$(ob_outputDirectory)/Inbox/${{platform}}' + ${{ if eq(platform, 'i686-pc-windows-msvc') }}: + targetFolder: '$(ob_createvpack_vpackdirectory)/i386' + ${{ elseif eq(platform, 'x86_64-pc-windows-msvc') }}: + targetFolder: '$(ob_createvpack_vpackdirectory)/amd64' + ${{ else }}: # aarch64-pc-windows-msvc + targetFolder: '$(ob_createvpack_vpackdirectory)/arm64' + contents: '*' + - task: CopyFiles@2 + # only do this once + displayName: Copy manifest to vpack + inputs: + sourceFolder: '$(ob_outputDirectory)/' + targetFolder: '$(ob_createvpack_vpackdirectory)/' + contents: 'instrumentation.man' diff --git a/.pipelines/convert-resx-to-rc.ps1 b/.pipelines/convert-resx-to-rc.ps1 new file mode 100644 index 0000000..90518bd --- /dev/null +++ b/.pipelines/convert-resx-to-rc.ps1 @@ -0,0 +1,354 @@ +# This script is used to move the resources from all the resx files in the +# directory (args[0]) to a .rc and .h file for use in C++ projects. (and a rust +# file for loading resources in Rust) + +# Root directory which contains the resx files +$parentDirectory = $args[0] + +# File name of the base resource.h which contains all the non-localized resource definitions +$baseHeaderFileName = $args[1] + +# Target file name of the resource header file, which will be used in code - Example: resource.h +$generatedHeaderFileName = $args[2] + +# File name of the base ProjectName.rc which contains all the non-localized resources +$baseRCFileName = $args[3] + +# Target file name of the resource rc file, which will be used in code - Example: ProjectName.rc +$generatedRCFileName = $args[4] + +# Target file name of the rust resource file, which will be used in code - Example: resource_ids.rs +$generatedRustFileName = $args[5] + +# Optional argument: Initial resource id in the resource header file. By default it is 101 +if ($args.Count -eq 7) +{ + $initResourceID = $args[6] +} +else +{ + $initResourceID = 101 +} + +# Flags to check if the first updated has occurred +$rcFileUpdated = $false +# $rustFileUpdated = $false + +# Output folder for the new resource files. It will be in ProjectDir\Generated Files so that the files are ignored by .gitignore +$generatedFilesFolder = $parentDirectory + "\Generated Files" + +# Create Generated Files folder if it doesn't exist +if (!(Test-Path -Path $generatedFilesFolder)) +{ + $paramNewItem = @{ + Path = $generatedFilesFolder + ItemType = 'Directory' + Force = $true + } + + New-Item @paramNewItem +} + +# Hash table to get the language codes from the code used in the file name +$languageHashTable = @{ + # This is the table straight from PowerToys + + # "ar" = @("ARA", "ARABIC", "NEUTRAL", "Arabic"); + # "bg" = @("BGR", "BULGARIAN", "NEUTRAL", "Bulgarian"); + # "ca" = @("CAT", "CATALAN", "NEUTRAL", "Catalan"); + # "cs" = @("CSY", "CZECH", "NEUTRAL", "Czech"); + # "de" = @("DEU", "GERMAN", "NEUTRAL", "German"); + # "en-US" = @("ENU", "ENGLISH", "ENGLISH_US", "English (United States)"); + # "es" = @("ESN", "SPANISH", "NEUTRAL", "Spanish"); + # "eu-ES" = @("EUQ", "BASQUE", "DEFAULT", "Basque (Basque)"); + # "fr" = @("FRA", "FRENCH", "NEUTRAL", "French"); + # "he" = @("HEB", "HEBREW", "NEUTRAL", "Hebrew"); + # "hu" = @("HUN", "HUNGARIAN", "NEUTRAL", "Hungarian"); + # "it" = @("ITA", "ITALIAN", "NEUTRAL", "Italian"); + # "ja" = @("JPN", "JAPANESE", "NEUTRAL", "Japanese"); + # "ko" = @("KOR", "KOREAN", "NEUTRAL", "Korean"); + # "nb-NO" = @("NOR", "NORWEGIAN", "NORWEGIAN_BOKMAL", "Norwegian Bokmål (Norway)"); + # "nl" = @("NLD", "DUTCH", "NEUTRAL", "Dutch"); + # "pl" = @("PLK", "POLISH", "NEUTRAL", "Polish"); + # "pt-BR" = @("PTB", "PORTUGUESE", "PORTUGUESE_BRAZILIAN", "Portuguese (Brazil)"); + # "pt-PT" = @("PTG", "PORTUGUESE", "PORTUGUESE", "Portuguese (Portugal)"); + # "ro" = @("ROM", "ROMANIAN", "NEUTRAL", "Romanian"); + # "ru" = @("RUS", "RUSSIAN", "NEUTRAL", "Russian"); + # "sk" = @("SKY", "SLOVAK", "NEUTRAL", "Slovak"); + # "sv" = @("SVE", "SWEDISH", "NEUTRAL", "Swedish"); + # "tr" = @("TRK", "TURKISH", "NEUTRAL", "Turkish"); + # "zh-CN" = @("CHS", "CHINESE", "NEUTRAL", "Chinese (Simplified)"); + # "zh-Hans" = @("CHS", "CHINESE", "NEUTRAL", "Chinese (Simplified)"); + # "zh-Hant" = @("CHT", "CHINESE", "CHINESE_TRADITIONAL", "Chinese (Traditional)") + # "zh-TW" = @("CHT", "CHINESE", "CHINESE_TRADITIONAL", "Chinese (Traditional)") + +# GENERATE ME WITH gen-lang-codes.ps1 +# +# the numbers in params 1 and 2 are the language and sublanguage values for the +# rc file's LANGUAGE statement. Usually those are defined in windows.h, but we +# can't just figure out what the constant in that file is just from a language +# code. I suppose this script could probably me modified to generate these +# values on the fly from the same code in gen-lang-codes.ps1, but I'm not sure +# it's worth it. + "af-ZA" = @("AFR", "1078", "0", "Afrikaans (South Africa)"); + "am-ET" = @("AMH", "1118", "0", "Amharic (Ethiopia)"); + "ar-SA" = @("ARA", "1025", "0", "Arabic (Saudi Arabia)"); + "as-IN" = @("ASM", "1101", "0", "Assamese (India)"); + "az-Latn-AZ" = @("AZE", "1068", "0", "Azerbaijani (Latin, Azerbaijan)"); + "bg-BG" = @("BUL", "1026", "0", "Bulgarian (Bulgaria)"); + "bn-IN" = @("BEN", "1093", "0", "Bangla (India)"); + "bs-Latn-BA" = @("BOS", "5146", "0", "Bosnian (Latin, Bosnia & Herzegovina)"); + "ca-ES" = @("CAT", "1027", "0", "Catalan (Spain)"); + "ca-Es-VALENCIA" = @("CAT", "2051", "0", "Catalan (Spain, Valencian)"); + "cs-CZ" = @("CES", "1029", "0", "Czech (Czechia)"); + "cy-GB" = @("CYM", "1106", "0", "Welsh (United Kingdom)"); + "da-DK" = @("DAN", "1030", "0", "Danish (Denmark)"); + "de-DE" = @("DEU", "1031", "0", "German (Germany)"); + "el-GR" = @("ELL", "1032", "0", "Greek (Greece)"); + "en-GB" = @("ENG", "2057", "0", "English (United Kingdom)"); + "en-US" = @("ENG", "1033", "0", "English (United States)"); + "es-ES" = @("SPA", "3082", "0", "Spanish (Spain)"); + "es-MX" = @("SPA", "2058", "0", "Spanish (Mexico)"); + "et-EE" = @("EST", "1061", "0", "Estonian (Estonia)"); + "eu-ES" = @("EUS", "1069", "0", "Basque (Spain)"); + "fa-IR" = @("FAS", "1065", "0", "Persian (Iran)"); + "fi-FI" = @("FIN", "1035", "0", "Finnish (Finland)"); + "fil-PH" = @("FIL", "1124", "0", "Filipino (Philippines)"); + "fr-CA" = @("FRA", "3084", "0", "French (Canada)"); + "fr-FR" = @("FRA", "1036", "0", "French (France)"); + "ga-IE" = @("GLE", "2108", "0", "Irish (Ireland)"); + "gd-gb" = @("GLA", "1169", "0", "Scottish Gaelic (United Kingdom)"); + "gl-ES" = @("GLG", "1110", "0", "Galician (Spain)"); + "gu-IN" = @("GUJ", "1095", "0", "Gujarati (India)"); + "he-IL" = @("HEB", "1037", "0", "Hebrew (Israel)"); + "hi-IN" = @("HIN", "1081", "0", "Hindi (India)"); + "hr-HR" = @("HRV", "1050", "0", "Croatian (Croatia)"); + "hu-HU" = @("HUN", "1038", "0", "Hungarian (Hungary)"); + "hy-AM" = @("HYE", "1067", "0", "Armenian (Armenia)"); + "id-ID" = @("IND", "1057", "0", "Indonesian (Indonesia)"); + "is-IS" = @("ISL", "1039", "0", "Icelandic (Iceland)"); + "it-IT" = @("ITA", "1040", "0", "Italian (Italy)"); + "ja-JP" = @("JPN", "1041", "0", "Japanese (Japan)"); + "ka-GE" = @("KAT", "1079", "0", "Georgian (Georgia)"); + "kk-KZ" = @("KAZ", "1087", "0", "Kazakh (Kazakhstan)"); + "km-KH" = @("KHM", "1107", "0", "Khmer (Cambodia)"); + "kn-IN" = @("KAN", "1099", "0", "Kannada (India)"); + "ko-KR" = @("KOR", "1042", "0", "Korean (Korea)"); + "kok-IN" = @("KOK", "1111", "0", "Konkani (India)"); + "lb-LU" = @("LTZ", "1134", "0", "Luxembourgish (Luxembourg)"); + "lo-LA" = @("LAO", "1108", "0", "Lao (Laos)"); + "lt-LT" = @("LIT", "1063", "0", "Lithuanian (Lithuania)"); + "lv-LV" = @("LAV", "1062", "0", "Latvian (Latvia)"); + "mi-NZ" = @("MRI", "1153", "0", "Māori (New Zealand)"); + "mk-MK" = @("MKD", "1071", "0", "Macedonian (North Macedonia)"); + "ml-IN" = @("MAL", "1100", "0", "Malayalam (India)"); + "mr-IN" = @("MAR", "1102", "0", "Marathi (India)"); + "ms-MY" = @("MSA", "1086", "0", "Malay (Malaysia)"); + "mt-MT" = @("MLT", "1082", "0", "Maltese (Malta)"); + "nb-NO" = @("NOB", "1044", "0", "Norwegian Bokmål (Norway)"); + "ne-NP" = @("NEP", "1121", "0", "Nepali (Nepal)"); + "nl-NL" = @("NLD", "1043", "0", "Dutch (Netherlands)"); + "nn-NO" = @("NNO", "2068", "0", "Norwegian Nynorsk (Norway)"); + "or-IN" = @("ORI", "1096", "0", "Odia (India)"); + "pa-IN" = @("PAN", "1094", "0", "Punjabi (India)"); + "pl-PL" = @("POL", "1045", "0", "Polish (Poland)"); + "pt-BR" = @("POR", "1046", "0", "Portuguese (Brazil)"); + "pt-PT" = @("POR", "2070", "0", "Portuguese (Portugal)"); + "qps-ploc" = @("", "1281", "0", "qps (Ploc)"); + "qps-ploca" = @("", "1534", "0", "qps (PLOCA)"); + "qps-plocm" = @("", "2559", "0", "qps (PLOCM)"); + "quz-PE" = @("", "3179", "0", "Quechua (Peru)"); + "ro-RO" = @("RON", "1048", "0", "Romanian (Romania)"); + "ru-RU" = @("RUS", "1049", "0", "Russian (Russia)"); + "sk-SK" = @("SLK", "1051", "0", "Slovak (Slovakia)"); + "sl-SI" = @("SLV", "1060", "0", "Slovenian (Slovenia)"); + "sq-AL" = @("SQI", "1052", "0", "Albanian (Albania)"); + "sr-Cyrl-BA" = @("SRP", "7194", "0", "Serbian (Cyrillic, Bosnia & Herzegovina)"); + "sr-Cyrl-RS" = @("SRP", "10266", "0", "Serbian (Cyrillic, Serbia)"); + "sr-Latn-RS" = @("SRP", "9242", "0", "Serbian (Latin, Serbia)"); + "sv-SE" = @("SWE", "1053", "0", "Swedish (Sweden)"); + "ta-IN" = @("TAM", "1097", "0", "Tamil (India)"); + "te-IN" = @("TEL", "1098", "0", "Telugu (India)"); + "th-TH" = @("THA", "1054", "0", "Thai (Thailand)"); + "tr-TR" = @("TUR", "1055", "0", "Turkish (Türkiye)"); + "tt-RU" = @("TAT", "1092", "0", "Tatar (Russia)"); + "ug-CN" = @("UIG", "1152", "0", "Uyghur (China)"); + "uk-UA" = @("UKR", "1058", "0", "Ukrainian (Ukraine)"); + "ur-PK" = @("URD", "1056", "0", "Urdu (Pakistan)"); + "uz-Latn-UZ" = @("UZB", "1091", "0", "Uzbek (Latin, Uzbekistan)"); + "vi-VN" = @("VIE", "1066", "0", "Vietnamese (Vietnam)"); + "zh-CN" = @("ZHO", "2052", "0", "Chinese (China)"); + "zh-TW" = @("ZHO", "1028", "0", "Chinese (Taiwan)"); + + } + +# Store the content to be written to a buffer +$rcFileContent = "" + +# Start by pre-populating the header file with a warning and the contents of the +# base header file. Do this only once, we'll append generated content to this +# later. +$headerFileContent = "// This file was auto-generated. Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.`r`n" +$rustFileContent = $headerFileContent; # The rust file doesn't have a base currently. We didn't need one. +try { + $headerFileContent += (Get-Content $parentDirectory\$baseHeaderFileName -Raw) +} +catch { + echo "Failed to read base header file." + exit 0 +} + +$lastResourceID = $initResourceID +# Iterate over all resx files in parent directory +Get-ChildItem $parentDirectory -Recurse -Filter *.resw | +Foreach-Object { + Write-Host "Processing $($_.FullName)" + $xmlDocument = $null + try { + $xmlDocument = [xml](Get-Content $_.FullName -ErrorAction:Stop) + } + catch { + Write-Host "Failed to load $($_.FullName)" + exit 0 + } + + # Get language code from file name + $lang = "en" + $tokens = $_.Name -split "\." + if ($tokens.Count -eq 3) { + $lang = $tokens[1] + } else { + $d = $_.Directory.Name + If ($d.Contains('-')) { # Looks like a language directory + $lang = $d + } + } + + $langData = $languageHashTable[$lang] + if ($null -eq $langData -and $lang.Contains('-')) { + # Modern Localization comes in with language + country tuples; + # we want to detect the language alone if we don't support the language-country + # version. + $lang = ($lang -split "-")[0] + $langData = $languageHashTable[$lang] + } + if ($null -eq $langData) { + Write-Warning "Unknown language $lang" + Return + } + + $newLinesForRCFile = "" + $newLinesForHeaderFile = "" + $newLinesForRustFile = "" + + try { + foreach ($entry in $xmlDocument.root.data) { + $culture = [System.Globalization.CultureInfo]::GetCultureInfo('en-US') + # Each resource is named as IDS_ResxResourceName, in uppercase. Escape occurrences of double quotes in the string + $lineInRCFormat = "IDS_" + $entry.name.ToUpper($culture) + " L`"" + $entry.value.Replace("`"", "`"`"") + "`"" + $newLinesForRCFile = $newLinesForRCFile + "`r`n " + $lineInRCFormat + + # Resource header & rust file needs to be updated only for one + # language - en-US, where our strings are first authored. This is to + # avoid duplicate entries in the header file. + if ($lang -eq "en-US") { + $lineInHeaderFormat = "#define IDS_" + $entry.name.ToUpper($culture) + " " + $lastResourceID.ToString() + $newLinesForHeaderFile = $newLinesForHeaderFile + "`r`n" + $lineInHeaderFormat + + $lineInRustFormat = "string_resources! { IDS_$($entry.name.ToUpper($culture)) = $($lastResourceID.ToString()); }" + + $newLinesForRustFile = $newLinesForRustFile + "`r`n" + $lineInRustFormat + + $lastResourceID++ + } + } + } + catch { + echo "Failed to read XML document." + exit 0 + } + + if ($newLinesForRCFile -ne "") { + # Add string table syntax + $newLinesForRCFile = "`r`nSTRINGTABLE`r`nBEGIN" + $newLinesForRCFile + "`r`nEND" + + $langStart = "`r`n/////////////////////////////////////////////////////////////////////////////`r`n// " + $langData[3] + " resources`r`n`r`n" + # $langStart += "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_" + $langData[0] + ")`r`nLANGUAGE LANG_" + $langData[1] + ", SUBLANG_" + $langData[2] + "`r`n" + $langStart += "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_" + $langData[0] + ")`r`nLANGUAGE " + $langData[1] + ", " + $langData[2] + "`r`n" + + $langEnd = "`r`n`r`n#endif // " + $langData[3] + " resources`r`n/////////////////////////////////////////////////////////////////////////////`r`n" + + $newLinesForRCFile = $langStart + $newLinesForRCFile + $langEnd + } + + # Initialize the rc file with an auto-generation warning and content from the base rc + if (!$rcFileUpdated) { + $rcFileContent = "// This file was auto-generated. Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.`r`n" + try { + $rcFileContent += (Get-Content $parentDirectory\$baseRCFileName -Raw) + } + catch { + echo "Failed to read base rc file." + exit 0 + } + $rcFileUpdated = $true + } + + # Add in the new string table to the rc file + $rcFileContent += $newLinesForRCFile + + # Here we deviate more from the original script. We've got multiple resw + # files to source, and we need to include the resource IDs from all of them + # in the final header (and .rs file). + # + # Our main resw file is the en-US one, so we only ever assembled additional + # header & rust lines in the case that the language for this resw file was + # en-US. + # + # Now that we have those lines, stick them in the header and rust files. + + $headerFileContent += $newLinesForHeaderFile + $rustFileContent += $newLinesForRustFile +} + +# Write to header file if the content has changed or if the file doesnt exist +try { + if (!(Test-Path -Path $generatedFilesFolder\$generatedHeaderFileName) -or (($headerFileContent + "`r`n") -ne (Get-Content $generatedFilesFolder\$generatedHeaderFileName -Raw))) { + Set-Content -Path $generatedFilesFolder\$generatedHeaderFileName -Value $headerFileContent -Encoding "utf8" + } + else { + # echo "Skipping write to generated header file" + } +} +catch { + echo "Failed to access generated header file." + exit 0 +} + +# Write to rc file if the content has changed or if the file doesnt exist +try { + if (!(Test-Path -Path $generatedFilesFolder\$generatedRCFileName) -or (($rcFileContent + "`r`n") -ne (Get-Content $generatedFilesFolder\$generatedRCFileName -Raw))) { + Set-Content -Path $generatedFilesFolder\$generatedRCFileName -Value $rcFileContent -Encoding "utf8" + } + else { + # echo "Skipping write to generated rc file" + } +} +catch { + echo "Failed to access generated rc file." + exit 0 +} + +# Write to rust file if the content has changed or if the file doesnt exist +try { + if (!(Test-Path -Path $generatedFilesFolder\$generatedRustFileName) -or (($rustFileContent + "`r`n") -ne (Get-Content $generatedFilesFolder\$generatedRustFileName -Raw))) { + Set-Content -Path $generatedFilesFolder\$generatedRustFileName -Value $rustFileContent -Encoding "utf8" + } + else { + # echo "Skipping write to generated header file" + } +} +catch { + echo "Failed to access generated rust file." + exit 0 +} diff --git a/.pipelines/daily-loc-build.yml b/.pipelines/daily-loc-build.yml new file mode 100644 index 0000000..6e834d5 --- /dev/null +++ b/.pipelines/daily-loc-build.yml @@ -0,0 +1,49 @@ +trigger: none +pr: none +schedules: + - cron: "0 3 * * 2-6" # Run at 03:00 UTC Tuesday through Saturday (After the work day in Pacific, Mon-Fri) + displayName: "Nightly Localization Build" + branches: + include: + - main + always: false # only run if there's code changes! + +pool: + vmImage: windows-2022 + +resources: + repositories: + - repository: self + type: git + ref: main + +steps: + +- checkout: self + clean: true + submodules: false + fetchDepth: 1 # Don't need a deep checkout for loc files! + fetchTags: false # Tags still result in depth > 1 fetch; we don't need them here + persistCredentials: true + +- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@3 + displayName: 'Touchdown Build - 92350, PRODEXT' + inputs: + teamId: 92350 + TDBuildServiceConnection: $(TouchdownServiceConnection) + authType: SubjectNameIssuer + resourceFilePath: | + **\en-US\*.resw + outputDirectoryRoot: LocOutput + appendRelativeDir: true + pseudoSetting: Included + +# Saving one of these makes it really easy to inspect the loc output... +- powershell: 'tar czf LocOutput.tar.gz LocOutput' + displayName: 'Archive Loc Output for Submission' + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: LocOutput' + inputs: + PathtoPublish: LocOutput.tar.gz + ArtifactName: LocOutput diff --git a/.pipelines/steps-fetch-and-prepare-localizations.yml b/.pipelines/steps-fetch-and-prepare-localizations.yml new file mode 100644 index 0000000..37e4861 --- /dev/null +++ b/.pipelines/steps-fetch-and-prepare-localizations.yml @@ -0,0 +1,23 @@ +parameters: + - name: includePseudoLoc + type: boolean + default: true + +steps: + - task: TouchdownBuildTask@3 + displayName: Download Localization Files + inputs: + teamId: 92350 + TDBuildServiceConnection: $(TouchdownServiceConnection) + authType: SubjectNameIssuer + resourceFilePath: | + **\en-US\*.resw + appendRelativeDir: true + localizationTarget: false + ${{ if eq(parameters.includePseudoLoc, true) }}: + pseudoSetting: Included + + - pwsh: |- + $Files = Get-ChildItem . -R -Filter 'Resources.resw' | ? FullName -Like '*en-US\*\Resources.resw' + $Files | % { Move-Item -Verbose $_.Directory $_.Directory.Parent.Parent -EA:Ignore } + displayName: Move Loc files into final locations diff --git a/Building.md b/Building.md new file mode 100644 index 0000000..5ad8e8e --- /dev/null +++ b/Building.md @@ -0,0 +1,75 @@ +# Building Sudo for Windows + +Sudo for Windows is a Rust project. If you're new to Rust, you can get started with the [Rust Book](https://doc.rust-lang.org/book/). You can quickly get started with rust by installing and running `rustup`: + +```cmd +winget install --id Rustlang.rustup --source winget +rustup update +``` + +## Building + +Rust is nice and straightforward. You can build sudo for the default architecture with a simple + +``` +cargo build +``` + +You may want to specify a specific architecture. To do that, you'll want instead: + +``` +cargo build --target x86_64-pc-windows-msvc +``` + +(You can also use `i686-pc-windows-msvc` as the target). + +### Running tests + +Assuming that you passed a target architecture above: + +``` +cargo test --target x86_64-pc-windows-msvc +``` + +We have additional manual tests that you can use to validate sudo in the +`tools\tests.ipynb` notebook. + +### Formatting and clippy + +``` +cargo fmt +cargo clippy +``` + +If your code passes a `cargo build && cargo test && cargo fmt && cargo clippy`, you're ready to send a PR. + +### Notes on building with the Microsoft internal toolchain + +When we're building this project internally, we need to use an internally-maintained fork of the rust toolchain. This toolchain needs to be used for all production work at Microsoft so we can stay compliant with Secure Development Lifecycle (SDL) requirements. + +**If you're external to Microsoft, this next section doesn't apply to you**. You +can use the standard Rust toolchain. + +First, install the internal `msrustup` toolchain to install the right version of +all our Rust tools. You can get it from the https://aka.ms/msrustup-win. After +that installs, then you'll probably also need to run the following: + +``` +rustup default ms-stable +``` + +That'll select the ms toolchain as the default. If you ever want to switch back, you can always just run + +``` +rustup default stable-x86_64-pc-windows-msvc +``` + +Additionally, we've got a separate fork of our `.cargo/config.toml` we need to use for internal builds. Notably, this includes `-Cehcont_guard` to enable EH Continuation Metadata. It also redirects cargo to use our own package feed for dependencies. + +You can manually build with that config with: + +``` +cargo build --config .cargo\ms-toolchain-config.toml +``` + +Note, if you run that on the public toolchain, you'll most definitely run into ``error: unknown codegen option: `ehcont_guard` `` when building. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a459aa8..477eb63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,5 @@ # Sudo for Windows Contributor's Guide -**Sudo for Windows is not currently open source**. We're still in the process of -crossing our "t"'s and dotting our "i"s. Stay tuned for more updates. In the -meantime, we are still accepting issues, feature requests, and contributions to -the [`sudo.ps1`] script. - Below is our guidance for how to report issues, propose new features, and submit contributions via Pull Requests (PRs). Our philosophy is heavily based around understanding the problem and scenarios first, this is why we follow this @@ -63,7 +58,52 @@ and fix them. However we currently don't accept community Pull Requests fixing localization issues. Localization is handled by the internal Microsoft team only. -### Repo Bot +## Contributing code + +As you might imagine, shipping something inside of Windows is a complicated +process--doubly so for a security component. There is a lot of validation and +paperwork that we don't really want to subject the community to. We want you to +be able to contribute easily and freely, and to let us deal with the paperwork. +We'll do our best to make sure this process is as seamless as possible. + +To support the community in building new feature areas for Sudo for Windows, +we're going to make extensive use of feature flags, to conditionally add new +features to Sudo for Windows. + +When contributing to Sudo for Windows, we will treat "bugfixes" and "features" +separately. Bug fixes can be merged into the codebase freely, so long as they +don't majorly change existing behaviors. New features will need to have their +code guarded by feature flag checks before we can accept them as contributions. + +As always, filing issues on the repo is the best way to have the team evaluate +if the change you're proposing would be considered a "bug fix" or "feature" +work. We will indicate which is which using the `Issue-Bug` and +`Issue-Feature`/`Issue-Task` labels. These labels are intended to be informative, +and may change throughout the lifecycle of a discussion. + +We'll be grouping sets of feature flags into different "branding"s throughout +the project. These brandings are as follows: +* **"Inbox"**: These are features that are included in the version of sudo that + ships with Windows itself. +* **"Stable"**: Features that ship in stable versions of sudo released here on + GitHub and on WinGet. +* **"Dev"**: The least stable features, which are only built in local development + builds. These are for work-in-progress features, that aren't quite ready for + public consumption + +All new features should be added under the "Dev" branding first. The core team +will then be responsible for moving those features into the appropriate branding +as we get internal signoffs. This will allow features to be worked on +continually in the open, while we slowly roll them into the OS product. We +unfortunately cannot provide timelines for when features will be able to move +from Stable into Inbox. Historical data showing that a feature has a track +record of being stable and secure is a great way for us to justify any +particular feature's inclusion into the product. + +If you're ready to jump in, head on over to [Building.md](./Building.md) to get +started. + +## Repo Bot The team triages new issues several times a week. During triage, the team uses labels to categorize, manage, and drive the project workflow. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dbe4db5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,516 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "embed-manifest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.49", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sudo" +version = "1.0.0" +dependencies = [ + "cc", + "clap", + "embed-manifest", + "sudo_events", + "which", + "win32resources", + "windows", + "windows-registry", + "winres", +] + +[[package]] +name = "sudo_events" +version = "0.1.0" +dependencies = [ + "win_etw_macros", + "win_etw_provider", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "w32-error" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7c61a6bd91e168c12fc170985725340f6b458eb6f971d1cf6c34f74ffafb43" +dependencies = [ + "winapi", +] + +[[package]] +name = "which" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "win32resources" +version = "0.1.0" + +[[package]] +name = "win_etw_macros" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec5071e615bb8b34c39dc852f82df53f29cb7108a93324a7101f7b2e6284b0e" +dependencies = [ + "proc-macro2", + "quote", + "sha1_smol", + "syn 1.0.109", + "uuid", + "win_etw_metadata", +] + +[[package]] +name = "win_etw_metadata" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50d0fa665033a19ecefd281b4fb5481eba2972dedbb5ec129c9392a206d652f" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "win_etw_provider" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f61e9dfafedf5eb4348902f2a32d326f2371245d05f012cdc67b9251ad6ea3" +dependencies = [ + "w32-error", + "widestring", + "win_etw_metadata", + "winapi", + "zerocopy", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-registry" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e885d2dff8cad07e7451b78eac1ff62f958825c4598d6ddf87e7d2661980c1c" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + +[[package]] +name = "winres" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +dependencies = [ + "toml", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.49", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8e89b9c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,45 @@ +[workspace] +resolver = "2" + +members = [ + "sudo", + "sudo_events", + "win32resources", +] + +# This list of dependencies allows us to specify version numbers for dependency in a single place. +# The dependencies in this list are _not_ automatically added to crates (Cargo.toml files). +# Each individual Cargo.toml file must explicitly declare its dependencies. To use a dependency +# from this list, specify "foo.workspace = true". For example: +# +# [dependencies] +# log.workspace = true +# +# See: https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table +# +[workspace.dependencies] +cc = "1.0" +# We're disabling the default features for clap because we don't need the +# "suggestions" feature. That provides really amazing suggestions for typos, but +# it unfortunately does not seem to support localization. +# +# To use clap at all, you do need the std feature enabled, so enable that. +# +# See: https://docs.rs/clap/latest/clap/_features/index.html +clap = { version = "4.4.7", default_features = false, features = ["std"] } +embed-manifest = "1.4" +which = "6.0" +win_etw_provider = "0.1.8" +win_etw_macros = "0.1.8" +windows = "0.54" +windows-registry = "0.1" +winres = "0.1" + +# For more profile settings, and details on the ones below, see https://doc.rust-lang.org/cargo/reference/profiles.html#profile-settings +[profile.release] +# Enable full debug info for optimized builds. +debug = "full" +# Split debuginfo into its own file to reduce binary size. +split-debuginfo = "packed" +lto = true +panic = "abort" diff --git a/cpp/logging/EventViewerLogging.c b/cpp/logging/EventViewerLogging.c new file mode 100644 index 0000000..1e57f2e --- /dev/null +++ b/cpp/logging/EventViewerLogging.c @@ -0,0 +1,7 @@ +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include + +#include + +#include "instrumentation.h" // Generated from manifest diff --git a/cpp/logging/README.md b/cpp/logging/README.md new file mode 100644 index 0000000..a380dc3 --- /dev/null +++ b/cpp/logging/README.md @@ -0,0 +1,28 @@ +## Helpful info for Event Viewer logging + +This C++ project logs to the Windows Event Viewer. It's all wired up to be called from Rust just the same as our RPC code. If you want to test changes here: + +1. Make sure to go change the `resourceFileName` and the `messageFileName` in + `instrumentation.man` to point at where the files are in your build + directory. (For me, that was + `D:\dev\private\sudo\target\x86_64-pc-windows-msvc\debug\sudo.exe`). It needs + to be the full path, so Event Viewer can find the exe (to load the resources + from it to know how to format the packet of binary data written to it) + - Make sure to change it back to `%systemroot%\System32\sudo.exe` before you push! +2. Make sure that Event Viewer is closed, and do + ```bat + wevtutil um cpp\logging\instrumentation.man + ``` + to remove the old manifest from event viewer +3. Build the project +4. Do a + ```bat + wevtutil im cpp\logging\instrumentation.man + ``` + to install the new manifest to event viewer +5. Open event viewer, and navigate to "Applications and Services Logs" -> + "Microsoft" -> "Windows" -> "Sudo" -> "Admin" + - alternatively: + ```bat + wevtutil qe Microsoft-Windows-Sudo/Admin /c:3 /rd:true /f:text + ``` diff --git a/cpp/logging/instrumentation.man b/cpp/logging/instrumentation.man new file mode 100644 index 0000000..0965d81 --- /dev/null +++ b/cpp/logging/instrumentation.man @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/rpc/README.md b/cpp/rpc/README.md new file mode 100644 index 0000000..610531a --- /dev/null +++ b/cpp/rpc/README.md @@ -0,0 +1,17 @@ +# Sudo RPC library + +To do local RPC, we need to use midl to generate function bindings for our RPC +interface, which is defined in `sudo_rpc.idl`. midl expects implementations and +callbacks via C functions which we can do in Rust, but there's one problem: +Error handling on the client side occurs with structed exceptions (SEH). +Those cannot be easily replicated in pure Rust and so the client side calls +are all wrapped in C functions. + +Changes here go as follows: +* Change the interface in `sudo_rpc.idl` +* Write a client-side wrapper in `RpcClient.c` in the style of the other ones +* Implement a client-side wrapper Rust in `rpc_bindings_client.rs` +* Implement the server-side part in `rpc_bindings_server.rs` + +Be careful about the function definitions. At the moment, there are no checks +in place that ensure that the Rust code matches the C code or the .idl file. diff --git a/cpp/rpc/RpcClient.c b/cpp/rpc/RpcClient.c new file mode 100644 index 0000000..9793081 --- /dev/null +++ b/cpp/rpc/RpcClient.c @@ -0,0 +1,82 @@ +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +// Our generated header file +#include "sudo_rpc.h" + +// Rust can't (easily) handle SEH exceptions, which the RPC however unfortunately uses. +// And so this wrapper C implementation exists. + +// From : +// Some RPC exceptions are already HRESULTs. Others are in the regular Win32 +// error space. If the incoming exception code isn't an HRESULT, wrap it. +inline HRESULT map_rpc_status(DWORD code) +{ + return IS_ERROR(code) ? code : HRESULT_FROM_WIN32(code); +} + +HRESULT seh_wrapper_client_DoElevationRequest( + RPC_IF_HANDLE binding, + HANDLE parent_handle, + const HANDLE* pipe_handles, + const HANDLE* file_handles, + DWORD sudo_mode, + UTF8_STRING application, + UTF8_STRING args, + UTF8_STRING target_dir, + UTF8_STRING env_vars, + GUID eventId, + HANDLE* child) +{ + RpcTryExcept + { + return client_DoElevationRequest( + binding, + parent_handle, + pipe_handles, + file_handles, + sudo_mode, + application, + args, + target_dir, + env_vars, + eventId, + child); + } + RpcExcept(RpcExceptionFilter(RpcExceptionCode())) + { + return map_rpc_status(RpcExceptionCode()); + } + RpcEndExcept; +} + +HRESULT seh_wrapper_client_Shutdown(RPC_IF_HANDLE binding) +{ + RpcTryExcept + { + client_Shutdown(binding); + return S_OK; + } + RpcExcept(RpcExceptionFilter(RpcExceptionCode())) + { + return map_rpc_status(RpcExceptionCode()); + } + RpcEndExcept; +} + +/******************************************************/ +/* MIDL allocate and free */ +/******************************************************/ + +void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len) +{ + return (malloc(len)); +} + +void __RPC_USER midl_user_free(void __RPC_FAR* ptr) +{ + free(ptr); +} diff --git a/cpp/rpc/sudo_rpc.acf b/cpp/rpc/sudo_rpc.acf new file mode 100644 index 0000000..fddd650 --- /dev/null +++ b/cpp/rpc/sudo_rpc.acf @@ -0,0 +1,5 @@ +[implicit_handle(handle_t sudo_rpc_IfHandle)] +interface sudo_rpc +{ + +} diff --git a/cpp/rpc/sudo_rpc.idl b/cpp/rpc/sudo_rpc.idl new file mode 100644 index 0000000..623ab33 --- /dev/null +++ b/cpp/rpc/sudo_rpc.idl @@ -0,0 +1,30 @@ +// This is where GUID is defined: +import "wtypesbase.idl"; + +typedef struct tagUTF8_STRING { + DWORD length; + [size_is(length)] const unsigned char *data; +} UTF8_STRING; + +[ + uuid (f691b703-f681-47dc-afcd-034b2faab911), // You must change this when you change the interface + version(1.0), +] +interface sudo_rpc +{ + HRESULT DoElevationRequest( + [in] handle_t binding, + [in, system_handle(sh_process)] HANDLE parent_handle, + [in, system_handle(sh_pipe), unique, size_is(3)] const HANDLE* pipe_handles, // in, out, err + [in, system_handle(sh_file), unique, size_is(3)] const HANDLE* file_handles, // in, out, err + [in] DWORD sudo_mode, + [in] UTF8_STRING application, + [in] UTF8_STRING args, // a null-delimited list + [in] UTF8_STRING target_dir, + [in] UTF8_STRING env_vars, // a null-delimited list + [in] GUID eventId, + [out, system_handle(sh_process)] HANDLE* child + ); + + void Shutdown([in] handle_t h1); +} diff --git a/docs/generate.bat b/docs/generate.bat new file mode 100644 index 0000000..d85c87a --- /dev/null +++ b/docs/generate.bat @@ -0,0 +1,28 @@ +@echo off + +@rem calculate the next version number based on whatever the last draft-xyz.docx file is +@rem this is a bit of a hack, but it works + +@rem get the last draft file +for /f "delims=" %%a in ('dir /b /on draft-*.docx') do set lastdraft=%%a + +@rem if we didn't find an existing one, start with 000 +if "%lastdraft%"=="" set lastdraft=draft-000.docx + +@rem get the version number from the last draft file +for /f "tokens=2 delims=-." %%a in ("%lastdraft%") do set /a version=%%a+1 + +echo Generating draft-%version%.docx... + +@rem create the new draft file +@rem +@rem mermaid-filter.cmd is from github.com/raghur/mermaid-filter. That's deeply +@rem out of date, so some of the newer features are missing. You can manually +@rem patch the mermaid.min.js if you want though. +pandoc -s -F mermaid-filter.cmd --from=markdown+yaml_metadata_block --to=docx .\draft.md -o .\draft-%version%.docx + +@rem delete mermaid-filter.err, if it's empty + +if exist mermaid-filter.err ( + for %%a in (mermaid-filter.err) do if %%~za==0 del mermaid-filter.err +) diff --git a/docs/obsidian-callouts.lua b/docs/obsidian-callouts.lua new file mode 100644 index 0000000..d88dd07 --- /dev/null +++ b/docs/obsidian-callouts.lua @@ -0,0 +1,17 @@ +local stringify = (require "pandoc.utils").stringify + +function BlockQuote (el) + start = el.content[1] + if (start.t == "Para" and start.content[1].t == "Str" and + start.content[1].text:match("^%[!%w+%][-+]?$")) then + _, _, ctype = start.content[1].text:find("%[!(%w+)%]") + el.content:remove(1) + start.content:remove(1) + div = pandoc.Div(el.content, {class = "callout"}) + div.attributes["data-callout"] = ctype:lower() + div.attributes["title"] = stringify(start.content):gsub("^ ", "") + return div + else + return el + end +end \ No newline at end of file diff --git a/docs/settings-app-toggle.png b/docs/settings-app-toggle.png new file mode 100644 index 0000000000000000000000000000000000000000..93b139c285aef666f92c6a172cca6f14cd53fbd7 GIT binary patch literal 30624 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 literal 0 HcmV?d00001 diff --git a/docs/sudo-terminal-multi.png b/docs/sudo-terminal-multi.png new file mode 100644 index 0000000000000000000000000000000000000000..27bfa3060329bc0a960adcc16db128acf9638877 GIT binary patch literal 45249 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 literal 0 HcmV?d00001 diff --git a/docs/sudo-terminal-redirected.png b/docs/sudo-terminal-redirected.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1565c8c081319e98f335204d4dceeca5c10639 GIT binary patch literal 40813 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

>J6OQo=U@e^&BTnFT<7I2Xt3)NY=pN z7eiVgP#*wOQPB}n)Vxmhl;dq#i?{uqKr6SoEsy%MNQkO14rhOw1nlKWWLr{=wq=$n zb89aseevR>pAlQL!*O#N~`4D#^fkALW3WVgj*7`=xULZvMw!6&quF!Ql>~_!>`!J*Ia=JX33GhJX2R zXJlqTk5RuZ<5ugt)3b)h>L5P-Co3#&nqrci9cZhp?JZp3mpHiK>z`ykfBT zq^^7ssp29?DQ(vV!q`hQM`-kvkjU4GTn*L5+SGfBzeLFFsiKz;yluYArwDi`uEgy3 z7YE6;OI3g*V9+SjWijiy(KKi3=us?)E!3t=-~`x6mhzsurJ!p57Y%h>krXLYa@m}cN`gV~`x>ZyJKKsw%Ad~UGt7{KAa9|8(b zxrvsvJ*iQVZzM?g)rDqqm2=p5(ySo>%Mavidz-e+^)%>vxjz8K)>owcN%X0Df;=!UGc1psmU^o`6lY;Izox3yA5;*wB zyPYPw=@92Ds-ZN59siKROS9NfJlo5Q)$Y%R=~`8YY7J!orYIM@O z(Y2FeEk09JzF+P9g?8BKM4#!ghcq(HK8%jH=^Mtfg}eOw%>nI1+!mP~O#6PJU_k){ zsxoy^yx8F&%E^C?$lMa?4ydi$GSB;XoD>BZn&*Vd?>!xDZlAx2(VGVn%3Kfoxou8y zS0jmm*Dzq#};R3kCz)aty#Aw5=U7G(SCP>=d`pZlJ$ql3v_u{Bw^2v5GhM zx@b`6Es*ePx)azsW+`f0;i{4)Ehhnb`c{1}YG%|u)bAL`|ij&+pW6>6m}lDvITxe5y7--H)8;Y zs00^bc^Tl~!F!FsDG&01)wZ($e%n}Y!;)38)Csg=E<1n0;4N?vp>M{}&DXs-Dh%P-_ncfC6UrsiD>%aHS}tj7T^Cc6qC=o0xkp*FU(@8nlV@ueujr3F060U~ECpC6WyA(Ky75?6yW$D>I` z=~XtGfz)tJM!F)8~xwv&=j$(|*W z-PmUACS{B4F~-=kGZ@<#W(>c3bk1k_emf};A ze~$k_J#8U>#P@!X-#~$>>`I~ZQ`Ry(%%yH7K(>C%x?%*lp;+T+{b+M26DNR{Fu^Md z?=TQae;uhp^wPU)N4=b^;2S7q*TTPoJ#o?4RbmBI@Zhd5Y4Cn6b145&9Bmm9gqqa_ zoLKDan?5~gN(10F-<02NMtNj5g{nhrlS>B;2lQDSMT7WQBV$LHhF#bkIXNbS!6b+t z2Y8FJro}3#J9G+p-4RxamD8Op>+b7Os zeE;vDzV8Ai+S%(+od>B}czy0$S1M(veIStgA2d*dU$QVa+tl|n7hu5mhnzzC-g?^J z`ykZOxuj9xgoCy35a1ur1~Fp)r~C5Z#WSUc=e(d6#LJymzs62g0f!xn@YEX-e5}j} zFPB#$T2ux%92q~ye8UUn1N0X{2WV<~(>_-!i_GlEQmxzorV$v#U7Zt`F*g$2hqc72 ze^=#y^OL`ild*S)yV!K=WD~$J#l0zsuGI=dp#*^!8s{A8a?k9?1`osM zqM-R-+^~WILXo^M{eAs7kjkmA@v$%-^(dWxkm|rp)`2<5!IkRWqq z+xQ;Ehm*k#=DvA#IL6-}KvD>P{^AS{a924%d*=8(;nL$L76CJ}seVPAYZW)Cpnso2 z`RanTwAN|}w4*Ycu^K0j4{8~mKTK!RfC)O$LF$e1v-bz;+3*3N0~)G;$aRli&c-f- z=_60D`U|RJzjm`>xcX?)U#Gz834!zgj>F=USV0ISFD-P{-`X!0zIdz&lfMFMY>ubJksEsX{CdEL-jV_l#I}pZM?|pgoFU(=qZ9iJiai zBO=<$#M$_q-_+tJR=okbqe0L8(P^p`#W^!z-EGeN;QL_Vw#d+Mu6*~B);27W9v#)q z_(1=QI}}Va?r>FBS{eYRtd2s0qV9XvpVMnVv2`%N^{( z%}=xYVE26;^q}ONcx1w=Yxi~ayku&nVaGLS!eyzH$~3MdUkm0#L!AK|c>H(9A9K9e z8)UVIY4s?Vi}99E({`~B(xZ^w%5Ltaouco==>~JMh82|R9xeG9UZG4FK${NKn)mtgoU8H6zX#r_erVI} z`DfJT_UB!7p0FyuxmV0ugv|f=0MQ4+3ZDGdn3wgG#{hlXt$o+4&rE`DBD(QMmd+oftQ2V%lY3yhmpvS@6 zLDCONXSdiIZ&6%pL4!R?hVAG3&?CM`QiUAzm#@|Ki6aOdy6fG z4JX`)PK13VX5>0L8{dEMfZT{YgkW6yGw!jIPTJF$VF0)MQw0U^#7BTRQHj(I=FERB z{Oiw3Z?%W>IDeyhm6v>_v*|!ihG3aM4|qk@K7kAZnCvb4rIxpsCEsEweN100W>E%Q zv<9E8z#-%IUR}WMNDo>U-d3vbuT7Xk6(&EChMoV6$$l=+ON< zTa+=`XBF|?|K_gOrpXvaK1=%4_rUEwg(5D4;xYW}SU`*>7H}k+E%EUt!!j(d$^Rli zKp@VH<*gbG9%)F?RMeBtHyii}U(;(eHpCH25^tFBmaF8y=CL){f`aduXA4PH@Ji1g zgBIoyUrOde`OW=$-eFF8sRjELKbd9@g?~$3X0~0r)H)Y0Xq6&yyWmU<;ZjVQ=vkSw z{|R)IUQgTx499fiZw+rT5B|_HrC&(QT=c!K>mS>7e|)U((gW1&&{xH$>n$Uf#sWCS zYIUm_jV?7;OLo56VX*6wF06>R?vDfSzE6hpqf)qgr4j8s>+v<4a|BLv&U81U(JTGd z3^wAp0g6Xjv;KdZrBf1nexe-QxXjbM!HlC~IcA`omdR9aq5hc>#|OJ2i`X{P7?o{D z_ZT%rqHzMBUG%J=e9}4+7RS;)t1Gwf5xM6aM^;sS4-WY2crfEj6Zz4t8&$$oA_20U# zxpl|Ry}oAlQN^eODdNDk`V+af$}yLn@fooiuX&VUwUNS>xCc}ZT{vB6M~a9-FX>-i z$4HNSbYt=<$B@6~jL^MPDek*98J!rxBMXye;5U%D^ZT?K%+OHjP~FF1MntGccTLWb zXzialqjlN!PV4!efllY3%$Jt)|C$x-S2T(aYzZ_D;46&x*x8-ml{D_>U*+uJ@eaH) zkYH9d2-Zm6R94q1tp>Hj5RG&7chwb^nX8&DPcTC$S;a<)f2;ipvk}P(+kF4SP5-k{ z9XnNw`7vOtT?3+NiKMZr1Cy&z8lj@QhD+B`hJ@P18l3lt4C%L7A+=G~`)NLIj_ccx zhTPrv3fPSph}cNc6VjSqKSl0Lh%n)}(n&ytmoSZtLFy~YG%_BE{ET8zaYb_>0cNxK za;$?h?rcEW#I#2oOVwv^p&+om4_Qb83{owK@Ss5TECtA89utW!I5m@@x|2J266s+O z;E}+C`~3jjXNo$lQuWFjD*1I##h1)AB0ol>pe#*WQB`n>pYqZ_k31T~QFgh_i2|`s zzlp+TPkr)1X4-^Rte0D|m{;wp*gZ}O3;@Cft`u~sW2cT5v=Ou- zU1T#|C)GGZvc6em!HY`I4H*Zww!lrN;&KnQix6Ku(KI=wm=leV;s^NOIS${uLYFJI zYp-xE1K%%5Hn!&yL(x}GeJgK~^y9?EpU%!pQtT3O`OdG-x8|*L#lJE&v>=c2xL!}2 z&r_{quUMW^^QMan*RW&W?_clzCrs1`VB=&Dq%yNQlw1KqZyjeaLpA;L{Oe$G8K;yn z0CQ}9n{Y}l?=QBx{Wc_K{=f&0FSO~v?OBOW?~fo!2hsH-PuHhbH+(t#u{T);Bk?41 zl&~VHCwh2ST|-J4zp{1n3tB5*mjpwzbHsA(JQ-M$h4x9fl&Zp91FPNa$yj$m+EEml*9A zQ(v?=^DUa){6bRC3$eqQgHc28cUKx2fDCa^HwEa_2K20NL;5AiYipouDDTPCA_A0) zPx`h#8%};(LNC?%5_ILk%Y1bGQ0zjYnxE(TM;{q(1F`+jYZ3N(-;r?Xj0tFI`*MJ< zqWjJF31^?_k?A6}$77Ke$x&TKn}l}DPtG#~?zY4`guz|f7U|idV?BGz^sL%d=1^Sg z>?V(p4s`81rcB{jp2HVAeL!Co*l-^Upp$WX%As>k-R1;vRp!ascD^UKa1$@3Ld0*+ zzITg)Ek|c9xi7AGD$fA&g$ZVK8-Rp_1@5==eC++4nO>;ZOkP~YLN+4Efa|F8dpD=G|$3vg-6R^>Mjk*U? zqG}u8;t*bmX?ehM^9;!G9I-a5Q@1wfT=RW7An zAn^a6U91~FO_=C<-6QRp;Oy#yRI8tD@gN_4U}RK{&B*wIX-!3t{qZ6`r?;tz%*5TZ zRW>v9ntQWa-&<~}{kGP#zk(U3vqzXH!JC+%06)oM=y)+_;Y?M{@WH>M^dlKSyzIs; zqN;ZO(N<+IXoPn(nkVB*y$C$(F?MTFsU1pv9H;g++XM5`m>xhrFH$> zKCplCJZqGp6jb$UewV6VM-Y`VkFmUxlRu-Db`8B$j;5isjh~N^)0D-#914F>b}2WA z7jopXU`u_qy5ABo4XefOrWEC{PgC2tAO5f0#~`-?;JfBw@U=?L1s$eSi6wa(!IS!h zd=OIIhVM0g0#{g5_>4qghn%GFyG$c!5^zL)-v%Tm>~ z?}_=CcC!RRfj?Sc)(mZ%S!e-UUlhXIXMFbT`FtF^@iN6?(`WK+bW$ieHH0Rf{H}_U z1RQw^VMPUgv0KMQAK|ajmfmrpA=TX95kz1jGeaA~yWm$IwzOg91?Gg}cdhi-NpqT- zTP#5#!1n~-r|3i{(~%XA33^lnihBs3_I&R5&}_@G+B-2t;iFpB>>`kE)1NHJ%Unq$ zM+Tr?@sf3!E(g!ojI9&4Du!Jn5KRt5K+~u0D-zN*6#Qv%2>OyIu%%`J{+zsvN7+*q z&|-eDw^=szZ|1~xT7IZ|#Us9Iy0TJuUr;xS#p>;EL_Gfua2%S}gtl>$+ZUc@x{+X> zkE~_g=3jI^3EoQoGs{wIw$Gsq6MRXnpj#gLDaed`M28<>RD@Igu&xHE`D z=~WLe^k=TA{;b}*uo`3LskMG#X|lqHiLL#XR`mNd*N+H_)#)8tQk*+%RDRofS08wwFAgH3%qxI0Bfh&cU0vpvgXFrIcF0kS~WC^cGqy%J- zAD)!Aco%z;YXpc2H0X}S=B&hkfB?BZ*B)0m9D1j{Tvj!U#}XazARf-ZE|4OyJ=mXS zG+ML9R>Dum!HWxFz|l@*Kk?!)al4j!s6T*eI?s_O(N1nG7>ma(4|v(%Gm#T&96;n1 zL}ojqSUX5#e&jla#6~>^c{3z~vk^OAU9c(Va zl5=3@a!+h5nnqLCvDEYwT`>-SzxnQ`qg>$d3}pLpOHI1ZLg>Ld`%1+C?=?HO>X=US zHic)%Kmv~0X3*E^G~XTYkiY==b)5;EpIRo17Na=F*{403XbbBOeF+u9*;tW%M9=Sh zyG08nv|EwA#vp`N-x*l1pKhtGINXLwoJ(4O9m^$iYoM6nYdF1_@C0C@O8AIC0FQ=b zRXqiVF;wB#H$*n7!KU$-_VM1hKs;)S?WLiZ1xvvFYa*;4>C2wA$z7gSzQBzKb6#&! zo_akCdF69~2;|5$26w~Tvv!{);>9Bt=?%_b50Ym>I_2J{q)8jR6WTHsV6rTzs@ax9 zhsi{OzN#--nH!T~6P2*4A5pzmtvGZ})#Osp^7{A7U4tMLJ-+PF=}wvW>M;$L?NZRX z0`oLDKagz7|AR^at9!JiM|&TaHPMvgZ#=MhO8#<+o@D%)$o>(NQc5!*E%I{-J(O}` z+_a9sWO+Xrx;Z;vQDVZUHNZ8e@P+$~yQMNz53g&wv+EyFY%!Zh;vbFISJfa>Hi<4o zFu_JNu!MJ#JbN?}T2JM?EXXB6ZyqE`C)3rF$&MwxEJg73Mwi0P*uAo8yy)^w1Y2Zz zu-0C;(H=#f-yQBjbbLJOf~G`9j8)hfFZZjCd1ap&@dpM4$yEF%N4FeqMl8Y@{bZeE z7%^@RB~$UW=O;(VV_O83WeNt@sn=qhuIlgKQ*mU+7l|GZbR?Vx+QQBoq_1P@CIF3c z29W~Pk$Ov4TyZYm9MD6@h4E-kfMgve&qik z8?q$cqcL|Ib0h!U%|!etRuj&6!y7%=$hPbk%nM%$jp`Cz{d$x<=FlhnU473#g(4nr zVPj-yw@O=(6&~y2S6hi5PdPBB>pxSSRtqGBUpn$n-c4BOdXDIh!2TwB-D73ak!|X3 zfJ}dxUPVf_GJ~07B7c5))4J7iuc@T=yo#c@mRBe&zdsuOYS(T zTHZt(e5jQSvM63Tm&M-I5&sFs5AGa}QOTguKO3A`D-BUMDI|2|#7jQSwat6S_J9tv z9Dv6y!q@u%?j3?b%vbu#z@^|Z+KcDC)4mm$mMt<5Js~>!rDUI9De6_-z1sUm)5GI^ zF3s<9gDA+MHlM6RUUTpK4o%W;(OS5B;n`JS3&!TAK?Zd_a3>H2XVvb3I|5cfhl!JGm@S#^*@#c`8oBnR80}Cl7f$V|*_9aJ?4*?2WQ4ulI8cjSi$Ol!yj7!>Tr9?Sd|ktXto4&{Z%pGUEH5 zRpCsK$4C8s=?f;6dri)?v=Ou90hGN2FngD^=W;fVLTuND{Jvd!p7F8E-p|CpzXF+Q&yciXc16d*IYVF-0B@nJbTq_Sn^KACl066SsCs^t(31}HoouKVi=2^)IWH}+hFe6Z5I3aw`9=qh4HC(0*Si}I9kW; z+w()pacl%G`n+D?!hnbSxS8D?eNrk2?IQR}K%uQe4``_Smw;9O>`3hZ$zOi{(I?z! zwk322`ON{&pYJJcH@N9EFU}1}^^MB)z1wQTU;2HD^ji8abGuZ`IJ2Z0RthtOZfH}{yrjN%>UVK(i5c`O^AXh{ZE+nhsSzp?AtcwFm{P7L>n z9i;<*HS9*nR_8Qn)Z~^&qn_XUCZ?xJS-Bnpq$I^n>9|~yhdRe{zPsMm=|8tLn|O`Q ze`@bMO5CE(jwRz{cL#skY)^i)tnu$j!G-=uuOjh3+BMgW|FiJi8uptYcX=jtw0mIq z^-kG)IUIFc{+a-Q8t=gqcTkCfR$iHf$?OA$kTLE`!J-awe12n8ysxxgFasc;j!%!I z=onBqU?9*A`@1y!-}NL=b`spuI`elR-6KkV#kkPSPC-GzNMGNY$t3~Jl!D7l%d1m1 zOuL9qMI0!I34;w)XS2upVz-^rV8f(NsJNPxg%!MAAm0CnP^%s&c-d?Tb0yw;$qdq< zI{N#{T053HlS~|M2t}^lRUwR%Q5DlW)OtLP2K>UXrt}INsc8HT%U}J>2F$>lN%MO+ z7k}O2D^STCG^FkT1>*Yr6{yL9U;fg0*&Xt z6}qkT3mnGvpf}IN?|>w8zee|8p6TdoJt*eeWrrcRF1g!!`SHM=#JJH`jscQP8OU z^i4s+K`6t~?U>rw49j1ka#icu?Vg_8e|^zpoX_*|j8gD7cPwGK9x9#)h-?rI9sy8} zv$fo@&v|o^&|tpFq{*0FIwsErv|_#^9&&)o>9Z~|DLy&(JAmrIBVPnC9YI6!cQ7_S zWWOmHQvp7;*Vdt?NWcyu6%x|Or|50nWE{ByW;>W|-y6Trd3z>vZqZphF_GN6&o6IY zj$N8}ZmzFqNoPNd5UCwdbN?3@?@MiOBi6@W-E4nssyE?v#rDr;_0kzY0F{C+Dgm%* z!qP0hVK{y==rI3Myh65-fB^Bk5r$%Dp@&|al2g(votV|CF*^Ce*vf*%52p+GTqEix zPXix9auR(F!^uv*P9An9)hzv`Ycp|rNO!FGn=30?q9iNC>tH>Vw zjJ8~u$)deUgDr6eVzQvaO?G1v_;@9>oTW zceq0@v--+`Lb5!o9`7BlGX?@`npEqBx)$b`0E>rbKibUd#@MwnHW#`_+2wj9rlhN2 z?meejXLeE7mB5)(h}nv3*VZ?MucB<8dOZ20LEdi~rtdEF=Z99EmCx$o`9h$HQ2${5 zH3-2_>1jg0^8>_cZVRmcjUa5PljMQg4OBE!37q)N0%~ckt}ZrY!wXpgWW2>&+3Q2# zf=0ssxt-%p(PsN9$_~evHvQ_uY=-#{rmsdNqPSlnH>J#ys%_4 z9EZvs75z3Btz?y|Cl!tbFy~Q{LOXLF9hyt{57TGD`d8u@HV{{GHbLimT4`*65AjWd zkn&`z<0PnrduVl89^1J0Vyi;b*UW48sF4!+X?~I$>Y@WvwJ&&uQf+nA;%Q$1nK+_{ zTA(=yU+auYtbIeuB<+v`=y!gfG21{f!EG}TGkyt+?;XPqUE7Y{f0Qqj!PGx|iq3T7 zI>Q0NCNLi8Ig@9p;OD4ShFn&Jk>{*V2R+y!a;rtYVvGjA0xb#~+{){c=sSlO`N2_w zt^9{JkF)OgLDYR4Rk9NTcKymwHXWXRP?N)Av3nJw-JnCECAi<)fO~@h&ZE5wo^`sH z0k%GbroSFcnDC1UfV2jOdEaIpIM@oV;D21vlK(zQ8YD#P1A@!VsZgb-GHd*&Ktydu zEx#^-d$$g1{i?pCQ#tmD--E4h@)p?mzi+SrUO32nzW@?6N{UHiPdT}s%`OaSP4!Eq zeP^XAY=38NV$GQc{ekEy`3s&6l3oYH-4yid#Nzk266s!T?+VFpFx+Ahzpe#bDS-9B zjliCTipb}{!Nr&6VD=GVh-pR|lKSymw*&&@$^UfnHq=8sFA^o{J(Dh8uG-O=#hTZT z-v2fY>1J&kfXA+lo3%1^yjA}k>=<-cA6Sxp_b-fS2?dq@AnE-Z025gvYhXYx%cD#L z1PIW|_|*>q&22|3oHZRloYju>->!#d&CpO)STGA(&+YdcK~td@=qpPB7t^Rqbm8Mz zl=$}rpZ>cT`;WbjWTmc|&J@A#;*NyIaGubrkCFzT3d#zkfEA2=nM2pnnV{4-0Yz$2qItbF)@i=-hDJ5ywVl9u zYBYD?6s_%t@jcP){Kun{Mhde#z!%zm0;Tw9!J|Q_dg2p()J4Q^0(1jcpGZaQJ1czX z{?$`|3l`+8lVTRN-X)yrwA)|S&7c50t`KUC-5iZbeq5dVufIsThwUQO8o=QnnP@e@ zA?^ZBTxFyCdT7-m5Pi>Y{HVLttX>_mKcGd`LV#$0BYRDb@0`91j3`X1G?_#^~ael922ME&L~ zBdOy1If$h}=(XQJp(Z^uwbmw;bD_Ns>eqmQ9A2dQ&dXM8 l*| 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 v_7Gt&yOQ+uCMo4lYrDn384iP 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 literal 0 HcmV?d00001 diff --git a/docs/sudo.png b/docs/sudo.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4d0043b006936c99c202c58b5004624c50ed94 GIT binary patch literal 9908 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 G`2Ro4@j_7m literal 0 HcmV?d00001 diff --git a/docs/versions.md b/docs/versions.md new file mode 100644 index 0000000..6ad1374 --- /dev/null +++ b/docs/versions.md @@ -0,0 +1,22 @@ +# Sudo for Windows - Versions + +There are a few different versions of Sudo for Windows - this doc aims to +outline the differences between them. Each includes different sets of code, and +releases in different cadences. + +* **"Inbox"**: This is the version of Sudo that ships with Windows itself. This + is the most stable version, and might only include a subset of the features in + the source code. This is delivered with the OS, via servicing upgrades. + - Build this version with `cargo build --no-default-features --features Inbox` +* **"Stable"**: The stable version of Sudo for Windows which ships out of this + repo. This can be installed side-by-side with the inbox version. + - Build this version with `cargo build --no-default-features --features Stable` +* **"Dev"**: This is a local-only build of sudo. This has all the bits of code + turned on, for the most up-to-date version of the code. + - Build this version with `cargo build` + +Dev builds are the default for local compilation, to make the development inner loop the +easiest. + +For more info, see "[Contributing code](./Contributing.md#contributing-code)" in +the contributors guide. diff --git a/enable_sudo.cmd b/enable_sudo.cmd new file mode 100644 index 0000000..796a7f0 --- /dev/null +++ b/enable_sudo.cmd @@ -0,0 +1,19 @@ +@echo off + +net session >nul 2>&1 +if %errorLevel% == 0 ( + goto :do_it +) + +echo You need to be admin to enable sudo! +goto :exit + +:do_it +echo Enabling sudo... + +set key=HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo +set value=Enabled +set data=3 +reg add "%key%" /v "%value%" /t REG_DWORD /d %data% /f + +:exit diff --git a/sudo/Cargo.toml b/sudo/Cargo.toml new file mode 100644 index 0000000..5c6743b --- /dev/null +++ b/sudo/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "sudo" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +build = "build.rs" + +[[bin]] +test = true +name = "sudo" + +[build-dependencies] +winres.workspace = true +cc.workspace = true +embed-manifest.workspace = true +which = { workspace = true } + +[dependencies] + +clap = { workspace = true, default_features = false, features = ["color", "help", "usage", "error-context"] } +which = { workspace = true } +windows-registry = { workspace = true } + +sudo_events = { path = "../sudo_events" } +win32resources = { path = "../win32resources" } + +[dependencies.windows] +workspace = true +features = [ + "Wdk_Foundation", + "Wdk_System_Threading", + "Win32_Foundation", + "Win32_Globalization", + "Win32_Security", + "Win32_Security_Authorization", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Diagnostics_Debug", + "Win32_System_Diagnostics_Etw", + "Win32_System_Environment", + "Win32_System_Kernel", + "Win32_System_Memory", + "Win32_System_Registry", + "Win32_System_Rpc", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", +] + +[features] +# We attempt to use feature flags in a similar way to how the rest of the +# Windows codebase does. We've got a set of "brandings", each which contain a +# set of feature flags. Each branding is a superset of the previous branding, +# and is progressively "less stable" that the previous. +# +# The idea is that we can build with a specific branding, and get all the +# features that are enabled for that branding, plus all the "more stable" ones. +# +# We default to "Dev" branding, which has all the code turned on. Call `cargo +# build --no-default-features --features Inbox` to just get the inbox build (for +# example). + +############################################ +# Feature flags +Feature_test_flag = [] # This is a test feature flag, to demo how they can be used. + +############################################ +# Branding +# Put each individual feature flag into ONE of the following brandings +Inbox = [] +Stable = ["Inbox"] +Dev = ["Stable", "Feature_test_flag"] + +# by default, build everything. This is a little different than you'd typically +# expect for a rust crate, but since we're not actually expecting anyone to be +# ingesting us as a crate, it's fine. +default = ["Dev"] + diff --git a/sudo/Resources/en-US/Sudo.resw b/sudo/Resources/en-US/Sudo.resw new file mode 100644 index 0000000..d7fe18e --- /dev/null +++ b/sudo/Resources/en-US/Sudo.resw @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Sudo for Windows + {Locked=qps-ploc,qps-ploca,qps-plocm} + + + Sudo for Windows. +Can be used to run a command as admin. When run, will prompt for confirmation with a User Accounts Control dialog. Currently only supports running commands as the admin user who confirms the dialog. + +If used to run a console application, sudo will return when the target process exits. +If used to run a graphical application (for example, notepad.exe), sudo will return immediately. + +In New Window mode, sudo will launch applications from the Windows directory in C:\\Windows\\System32. You can use the --chdir option to change the working directory before running the command. + +If a mode is omitted, sudo will use the mode set in the system settings. If a mode is specified with one of --new-window, --disable-input, or --inline, sudo will exit with an error if that mode is not currently allowed by the system settings. + + {Locked="sudo","notepad.exe","C:\\Windows\\System32","--chdir"} + + + Sudo is disabled on this machine. To enable it, go to the \x1b]8;;ms-settings:developers\x1b\\Developer Settings page\x1b]8;;\x1b\\ in the Settings app + {Locked="\x1b]8;;ms-settings:developers\x1b\\","\x1b]8;;\x1b\\"} + + + Print help (see less with '-h') + {Locked="-h"} + + + Print help (see more with '--help') + {Locked="--help"} + + + Print version + + + The elevate subcommand is for internal use only + {Locked="elevate"} + + + Parent process ID + + + Command-line to run + + + Get current configuration information of sudo + + + Run a command as admin + + + Pass the current environment variables to the command + + + Use a new window for the command + + + Run in the current terminal, with input to the target application disabled + + + Run in the current terminal + Description of a command-line flag that will cause sudo to run the target application in the current console window. + + + Command-line to run + + + Sudo is disabled by your organization's policy. + + + Sudo is disabled on this machine. + + + Invalid mode: + This string will be followed by a string the user entered (which was and invalid value) + + + Error setting mode: + This string will be followed by an error message + + + Unknown error: + This string will be followed by an error message + + + Sudo is currently in Force New Window mode on this machine + + + Sudo is currently in Disable Input mode on this machine + Message printed to the user informing them of the current sudo mode. In this case, the mode is the "Disable Input" mode. + + + Sudo is currently in Inline mode on this machine + + + You must run this command as an administrator. + + + You cannot set a mode higher than Force New Window mode on this machine + Error message printed when the user attempts to set sudo into a mode higher than the mode currently allowed by policy. In this case, the currently allowed mode is the "Force New Window" mode. + + + You cannot set a mode higher than Disable Input mode on this machine + Error message printed when the user attempts to set sudo into a mode higher than the mode currently allowed by policy. In this case, the currently allowed mode is the "Disable Input" mode. + + + You cannot set a mode higher than Inline mode on this machine + Error message printed when the user attempts to set sudo into a mode higher than the mode currently allowed by policy. In this case, the currently allowed mode is the "Inline" mode. + + + Sudo mode set to Force New Window mode + + + Sudo mode set to DisableInput mode + + + Sudo mode set to Inline mode + + + Command not found + + + Operation was cancelled by the user + + + Launched {0} in a new window. + {0} will be replaced by the name of a command-line executable + + + Change the working directory before running the command + + + You cannot run in a mode higher than Force New Window mode on this machine + Error message printed when the user requested a mode higher than the currently allowed mode. In this case, the currently allowed mode is the "Force New Window" mode. + + + You cannot run in a mode higher than Disable Input mode on this machine + Error message printed when the user requested a mode higher than the currently allowed mode. In this case, the currently allowed mode is the "Disable Input" mode. + + + You cannot run in a mode higher than Inline mode on this machine + Error message printed when the user requested a mode higher than the currently allowed mode. In this case, the currently allowed mode is the "Inline" mode. + + + You are not allowed to preserve environment variables + Error message printed when the user tries to preserve environment variables, but is not allowed to + + + You are not allowed to run sudo + Error message printed when the user is not allowed to run sudo because they aren't an administrator + + + set USERPROFILE variable to target user's USERPROFILE + {Locked="USERPROFILE"} Help text for a commandline arg that sets the USERPROFILE variable + + diff --git a/sudo/build.rs b/sudo/build.rs new file mode 100644 index 0000000..d4688c4 --- /dev/null +++ b/sudo/build.rs @@ -0,0 +1,280 @@ +use embed_manifest::embed_manifest_file; +use std::path::PathBuf; +use std::process::Command; +use { + std::{env, io}, + winres::WindowsResource, +}; + +fn get_sdk_path() -> Option { + let mut sdk_path: Option = None; + let target = env::var("TARGET").unwrap(); + + // For whatever reason, find_tool doesn't work directly on `midl.exe`. It + // DOES work however, on `link.exe`, and will hand us back a PATH that has + // the path to the appropriate midl.exe in it. + let link_exe = cc::windows_registry::find_tool(target.as_str(), "link.exe") + .expect("Failed to find link.exe"); + link_exe.env().iter().for_each(|(k, v)| { + if k == "PATH" { + let elements = (v.to_str().expect("path exploded")) + .split(';') + .collect::>(); + // iterate over the elements to find one that starts with + // "C:\Program Files (x86)\Windows Kits\10\bin\10.0.*" + for element in elements { + if element.starts_with("C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.") { + sdk_path = Some(element.to_string()); + } + } + } + }); + sdk_path +} + +fn get_sdk_tool(sdk_path: &Option, tool_name: &str) -> String { + // seems like, in a VS tools prompt, midl.exe is in the path so the above + // doesn't include the path. kinda weird but okay? + let tool_path = match sdk_path { + Some(s) => PathBuf::from(s) + .join(tool_name) + .to_str() + .unwrap() + .to_owned(), + None => { + // This is the case that happens when you run the build from a VS + // tools prompt. In this case, the tool is already in the path, so + // we can just get the absolute path to the exe using the windows + // path search. + + let tool_path = which::which(tool_name).expect("Failed to find tool in path"); + tool_path.to_str().unwrap().to_owned() + } + }; + tool_path +} + +fn build_rpc() { + // Build our RPC library + + let sdk_path: Option = get_sdk_path(); + let midl_path = get_sdk_tool(&sdk_path, "midl.exe"); + + // Now, we need to get the path to the shared include directory, which is + // dependent on the SDK version. We're gonna find it based on the midl we + // already found. + // + // Our midl path is now something like: + // C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\midl.exe + // + // We want to get the path to the shared include directory, which is like + // + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared + // + // (of course, the version number will change depending on the SDK version) + // So, just take that path, remove two elements from the end, replace bin with Include, and add shared. + let mut include_path = PathBuf::from(midl_path.clone()); + include_path.pop(); + include_path.pop(); + // now we're at C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0 + let copy_of_include_path = include_path.clone(); + let version = copy_of_include_path.file_name().unwrap().to_str().unwrap(); + include_path.pop(); + include_path.pop(); + // now we're at C:\Program Files (x86)\Windows Kits\10\ + include_path.push("Include"); + include_path.push(version); + include_path.push("shared"); + + println!("midl_path: {:?}", midl_path); + + let target = env::var("TARGET").unwrap(); + + let cl_path = + cc::windows_registry::find_tool(target.as_str(), "cl.exe").expect("Failed to find cl.exe"); + // add cl.exe to our path + let mut path = std::env::var("PATH").unwrap(); + path.push(';'); + path.push_str(cl_path.path().parent().unwrap().to_str().unwrap()); + std::env::set_var("PATH", path); + + // Great! we've now finally got a path to midl.exe, and cl.exe is on the PATH + + // Now we can actually run midl.exe, to compile the IDL file. This will + // generate a bunch of files in the OUT_DIR which we need to do RPC. + + let mut cmd = std::process::Command::new(midl_path); + cmd.arg("../cpp/rpc/sudo_rpc.idl"); + cmd.arg("/h").arg("sudo_rpc.h"); + cmd.arg("/target").arg("NT100"); // LOAD BEARING: Enables system_handle + cmd.arg("/acf").arg("../cpp/rpc/sudo_rpc.acf"); + cmd.arg("/prefix").arg("client").arg("client_"); + cmd.arg("/prefix").arg("server").arg("server_"); + cmd.arg("/oldnames"); + cmd.arg("/I").arg(include_path); + + // Force midl to use the right architecture depending on our Rust target. + cmd.arg("/env").arg(match target.as_str() { + "x86_64-pc-windows-msvc" => "x64", + "i686-pc-windows-msvc" => "win32", + "aarch64-pc-windows-msvc" => "arm64", + _ => panic!("Unknown target {}", target), + }); + + // I was pretty confident that we needed to pass /protocol ndr64 here, but + // if we do that it'll break the win32 build. Omitting it entirely seems to + // Just Work. + // cmd.arg("/protocol").arg("ndr64"); + + cmd.arg("/out").arg(env::var("OUT_DIR").unwrap()); + + println!("Full midl command: {:?}", cmd); + let mut midl_result = cmd.spawn().expect("Failed to run midl.exe"); + println!( + "midl.exe returned {:?}", + midl_result.wait().expect("midl.exe failed") + ); + + // Now that our PRC header and stubs were generated, we can compile them + // into our actual RPC lib. + let mut rpc_build = cc::Build::new(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + rpc_build + .warnings(true) + .warnings_into_errors(true) + .include(env::var("OUT_DIR").unwrap()) + .file(out_dir.join("sudo_rpc_c.c")) + .file(out_dir.join("sudo_rpc_s.c")) + .file("../cpp/rpc/RpcClient.c") + .flag("/guard:ehcont"); + + println!("build cmdline: {:?}", rpc_build.get_compiler().to_command()); + rpc_build.compile("myRpc"); + println!("cargo:rustc-link-lib=myRpc"); + + println!("cargo:rerun-if-changed=../cpp/rpc/RpcClient.c"); + println!("cargo:rerun-if-changed=../cpp/rpc/sudo_rpc.idl"); +} + +fn build_logging() { + // Build our Event Logging library + + let sdk_path: Option = get_sdk_path(); + let mc_path = get_sdk_tool(&sdk_path, "mc.exe"); + + println!("mc_path: {:?}", mc_path); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + let mut cmd = std::process::Command::new(mc_path); + cmd.arg("-h").arg(&out_dir); + cmd.arg("-r").arg(&out_dir); + cmd.arg("../cpp/logging/instrumentation.man"); + + println!("Full mc command: {:?}", cmd); + + let mc_result = cmd + .spawn() + .expect("Failed to run mc.exe") + .wait() + .expect("mc.exe failed"); + if !mc_result.success() { + eprintln!("\n\nerror occurred: {}\n\n", mc_result); + std::process::exit(1); + } + + let mut logging_build = cc::Build::new(); + logging_build + .warnings(true) + .warnings_into_errors(true) + .include(env::var("OUT_DIR").unwrap()) + .file("../cpp/logging/EventViewerLogging.c") + .flag("/guard:ehcont"); + + println!( + "build cmdline: {:?}", + logging_build.get_compiler().to_command() + ); + logging_build.compile("myEventLogging"); + println!("cargo:rustc-link-lib=myEventLogging"); + + println!("cargo:rerun-if-changed=../cpp/logging/EventViewerLogging.c"); + println!("cargo:rerun-if-changed=../cpp/logging/instrumentation.man"); +} + +fn main() -> io::Result<()> { + // Always build the RPC lib. + build_rpc(); + + // Always build the Event Logging lib. + build_logging(); + + println!("cargo:rerun-if-changed=sudo/Resources/en-US/Sudo.resw"); + println!("cargo:rerun-if-changed=sudo.rc"); + println!("cargo:rerun-if-changed=../Generated Files/out.rc"); + println!("cargo:rerun-if-changed=../Generated Files/out_resources.h"); + + // compile the resource file. + // Run + // powershell -c .pipelines\convert-resx-to-rc.ps1 .\ no_existy.h res.h no_existy.rc out.rc resource_ids.rs + // to generate the resources + + Command::new("powershell") + .arg("-NoProfile") + .arg("-c") + .arg("..\\.pipelines\\convert-resx-to-rc.ps1") + .arg("..\\") // Root directory which contains the resx files + .arg("no_existy.h") // File name of the base resource.h which contains all the non-localized resource definitions + .arg("resource.h") // Target file name of the resource header file, which will be used in code - Example: resource.h + .arg("sudo\\sudo.rc") // File name of the base ProjectName.rc which contains all the non-localized resources + .arg("out.rc") // Target file name of the resource rc file, which will be used in code - Example: ProjectName.rc + .arg("resource_ids.rs") // Target file name of the rust resource file, which will be used in code - Example: resource.rs + .status() + .expect("Failed to generate resources"); + + // witchcraft to get windows.h from the SDK to be able to be found, for the resource compiler + let target = std::env::var("TARGET").unwrap(); + if let Some(tool) = cc::windows_registry::find_tool(target.as_str(), "cl.exe") { + for (key, value) in tool.env() { + std::env::set_var(key, value); + } + } + + if std::env::var_os("CARGO_CFG_WINDOWS").is_some() { + // TODO:MSFT + // Re-add the following: + // + // detached + // + // to our manifest + embed_manifest_file("sudo.manifest").expect("Failed to embed manifest"); + + let generated_rc_content = std::fs::read_to_string("../Generated Files/out.rc").unwrap(); + let instrumentation_rc_content = + std::fs::read_to_string(env::var("OUT_DIR").unwrap() + "/instrumentation.rc").unwrap(); + let generated_header = std::fs::read_to_string("../Generated Files/resource.h").unwrap(); + WindowsResource::new() + // We don't want to use set_resource_file here, because we _do_ want + // the file version info that winres can autogenerate. Instead, + // manually stitch in our generated header (with resource IDs), and + // our generated rc file (with the localized strings) + .append_rc_content(&generated_header) + .append_rc_content(&instrumentation_rc_content) + .append_rc_content(&generated_rc_content) + .compile()?; + } + Ok(()) +} + +// Magic incantation to get the build to generate the .rc file, before we build things: +// +// powershell -c .pipelines\convert-resx-to-rc.ps1 src\cascadia\ this_doesnt_exist.h out_resources.h no_existy.rc out.rc resource_ids.rs +// +// do that from the root of the repo, and it will generate the .rc file, into +// src\cascadia\Generated Files\{out.rc, out_resources.h} +// +// +// Alternatively, +// powershell -c .pipelines\convert-resx-to-rc.ps1 .\ no_existy.h res.h no_existy.rc out.rc resource_ids.rs +// +// will generate the .rc file into the a "Generated Files" dir in the root of the repo. diff --git a/sudo/src/elevate_handler.rs b/sudo/src/elevate_handler.rs new file mode 100644 index 0000000..8fada22 --- /dev/null +++ b/sudo/src/elevate_handler.rs @@ -0,0 +1,167 @@ +use crate::helpers::*; +use crate::logging_bindings::event_log_request; +use crate::messages::ElevateRequest; +use crate::rpc_bindings_server::rpc_server_setup; +use crate::tracing; +use std::ffi::CString; +use std::os::windows::io::{FromRawHandle, IntoRawHandle}; +use std::os::windows::process::CommandExt; +use std::process::Stdio; +use windows::{ + core::*, Win32::Foundation::*, Win32::System::Console::*, Win32::System::Threading::*, +}; + +fn handle_to_stdio(h: HANDLE) -> Stdio { + if h.is_invalid() { + return Stdio::inherit(); + } + + unsafe { + let p = GetCurrentProcess(); + let mut clone = Default::default(); + match DuplicateHandle(p, h, p, &mut clone, 0, true, DUPLICATE_SAME_ACCESS) { + Ok(_) => Stdio::from_raw_handle(clone.0 as _), + Err(_) => Stdio::null(), + } + } +} + +/// Prepare the target process, spawn it, and hand back the Child process. This will take care of setting up the handles for redirected input/output, and setting the environment variables. +pub fn spawn_target_for_request(request: &ElevateRequest) -> Result { + tracing::trace_log_message(&format!("Spawning: {}...", &request.application)); + + let mut command_args = std::process::Command::new(request.application.clone()); + + command_args.current_dir(request.target_dir.clone()); + command_args.args(request.args.clone()); + + tracing::trace_log_message(&format!("args: {:?}", &request.args)); + + if !request.env_vars.is_empty() { + command_args.env_clear(); + command_args.envs(env_from_raw_bytes(&request.env_vars)); + } + + // If we're in ForceNewWindow mode, we want the target process to use a new + // console window instead of inheriting the one from the parent process. + if request.sudo_mode == SudoMode::ForceNewWindow { + command_args.creation_flags(CREATE_NEW_CONSOLE.0); + } + + // Set the stdin/stdout/stderr of the child process In disabled input + // mode, set stdin to null. We don't want the target application to be + // able to read anything from stdin. + command_args.stdin(if request.sudo_mode != SudoMode::DisableInput { + handle_to_stdio(request.handles[0]) + } else { + Stdio::null() + }); + command_args.stdout(handle_to_stdio(request.handles[1])); + command_args.stderr(handle_to_stdio(request.handles[2])); + + command_args.spawn().map_err(|err| { + match err.kind() { + std::io::ErrorKind::NotFound => { + // This error code is MSG_DIR_BAD_COMMAND_OR_FILE. That's + // what CMD uses to indicate a command not found. + E_DIR_BAD_COMMAND_OR_FILE.into() + } + _ => err.into(), + } + }) +} + +/// Execute the elevation request. +/// * Conditionally attach to the parent process's console (if requested) +/// * Spawn the target process (with redirected input/output if requested, and with the environment variables passed in if needed) +/// Called by rust_handle_elevation_request +pub fn handle_elevation_request(request: &ElevateRequest) -> Result { + // Log the request we received to the event log. This should create a pair + // of events, one for the request, and one for the response, each with the + // same RequestID. + event_log_request(false, request); + + // Check if the requested sudo mode is allowed + let config: RegistryConfigProvider = Default::default(); + let allowed_mode = get_allowed_mode(&config)?; + if request.sudo_mode > allowed_mode { + tracing::trace_log_message(&format!( + "Requested sudo mode is not allowed: {:?} ({:?})", + request.sudo_mode, allowed_mode + )); + return Err(E_ACCESSDENIED.into()); + } + + // If we're in ForceNewWindow mode, we _don't_ want to detach from our + // current console and reattach to the parent process's console. Instead, + // we'll just create the target process with CREATE_NEW_CONSOLE. + // + // This scenario only happens when we're running `sudo -E --newWindow`, to + // copy env vars but also use a new console window. If we don't pass -E, + // then we'll have instead just directly ShellExecute'd the target + // application (and never hit this codepath) + // + // Almost all the time, we'll actually hit the body of this conditional. + if request.sudo_mode != SudoMode::ForceNewWindow { + // It would seem that we always need to detach from the current console, + // even in redirected i/o mode. In the case that we aren't fully redirected + // (like, if stdin is redirected but stdout isn't), we'll still need to + // attach to the parent console for the other std handles. + + unsafe { + // Detach from the current console + _ = FreeConsole(); + + // Attach to the parent process's console + if { AttachConsole(request.parent_pid) }.is_ok() { + // Add our own CtrlC handler, so that we can ignore it. + _ = SetConsoleCtrlHandler(Some(ignore_ctrl_c), true); + // TODO! add some error handling here you goober + } + } + } + + // We're attached to the right console, Run the command. + let process_launch = spawn_target_for_request(request); + unsafe { + _ = SetConsoleCtrlHandler(Some(ignore_ctrl_c), false); + _ = FreeConsole(); + } + + let child = process_launch?; + + // Limit the things the caller can do with the process handle, because the one we just created is PROCESS_ALL_ACCESS. + // I tried to use [out, system_handle(sh_process, PROCESS_QUERY_LIMITED_INFORMATION)] + // in the COM API to have it limit the handle permissions but that didn't work at all. + // So now we do it manually here. + unsafe { + let mut child_handle = OwnedHandle::default(); + let current_process = GetCurrentProcess(); + DuplicateHandle( + current_process, + HANDLE(child.into_raw_handle() as _), + current_process, + &mut *child_handle, + (PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_DUP_HANDLE | PROCESS_SYNCHRONIZE).0, + false, + DUPLICATE_CLOSE_SOURCE, + )?; + Ok(child_handle) + } +} + +/// Starts the RPC server and blocks until Shutdown() is called. +pub fn start_rpc_server( + parent_pid: u32, + _caller_sid: Option<&String>, + _args: &[&String], +) -> Result { + // TODO:48520593 In rust_handle_elevation_request, validate that the parent + // process handle is the same one that we opened here. + + let endpoint = generate_rpc_endpoint_name(parent_pid); + let endpoint = CString::new(endpoint).unwrap(); + rpc_server_setup(&endpoint, parent_pid)?; + + Ok(0) +} diff --git a/sudo/src/helpers.rs b/sudo/src/helpers.rs new file mode 100644 index 0000000..029fda5 --- /dev/null +++ b/sudo/src/helpers.rs @@ -0,0 +1,762 @@ +use crate::rpc_bindings::Utf8Str; +use crate::trace_log_message; +use std::ffi::{OsStr, OsString}; +use std::fs::File; +use std::mem::{size_of, MaybeUninit}; +use std::ops::{Deref, DerefMut}; +use std::os::windows::ffi::OsStringExt; +use std::os::windows::fs::FileExt; +use std::path::{Path, PathBuf}; +use std::slice::{from_raw_parts, from_raw_parts_mut}; +use windows::Win32::Storage::FileSystem::GetFullPathNameW; +use windows::Win32::System::Diagnostics::Debug::{IMAGE_NT_HEADERS32, IMAGE_SUBSYSTEM}; +use windows::Win32::System::Environment::{FreeEnvironmentStringsW, GetEnvironmentStringsW}; +use windows::Win32::System::Rpc::RPC_STATUS; +use windows::Win32::System::SystemServices::{ + IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE, SE_TOKEN_USER, SE_TOKEN_USER_1, +}; +use windows::{ + core::*, Win32::Foundation::*, Win32::Security::Authorization::*, Win32::Security::*, + Win32::System::Console::*, Win32::System::Threading::*, +}; + +// https://github.com/microsoft/win32metadata/issues/1857 +pub const RPC_S_ACCESS_DENIED: RPC_STATUS = RPC_STATUS(ERROR_ACCESS_DENIED.0 as i32); + +pub const E_FILENOTFOUND: HRESULT = ERROR_FILE_NOT_FOUND.to_hresult(); +pub const E_CANCELLED: HRESULT = ERROR_CANCELLED.to_hresult(); +pub const MSG_DIR_BAD_COMMAND_OR_FILE: WIN32_ERROR = WIN32_ERROR(9009); +pub const E_DIR_BAD_COMMAND_OR_FILE: HRESULT = MSG_DIR_BAD_COMMAND_OR_FILE.to_hresult(); +pub const E_ACCESS_DISABLED_BY_POLICY: HRESULT = ERROR_ACCESS_DISABLED_BY_POLICY.to_hresult(); + +#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)] +pub enum SudoMode { + Disabled = 0, + ForceNewWindow = 1, + DisableInput = 2, + Normal = 3, +} + +impl TryFrom for SudoMode { + type Error = Error; + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(SudoMode::Disabled), + 1 => Ok(SudoMode::ForceNewWindow), + 2 => Ok(SudoMode::DisableInput), + 3 => Ok(SudoMode::Normal), + _ => Err(ERROR_INVALID_PARAMETER.into()), + } + } +} + +impl From for u32 { + fn from(value: SudoMode) -> Self { + value as u32 + } +} + +impl From for i32 { + fn from(val: SudoMode) -> Self { + val as i32 + } +} + +#[repr(transparent)] +#[derive(Default)] +pub struct OwnedHandle(pub HANDLE); + +impl OwnedHandle { + pub fn new(handle: HANDLE) -> Self { + OwnedHandle(handle) + } +} + +impl Drop for OwnedHandle { + fn drop(&mut self) { + if !self.0.is_invalid() { + unsafe { _ = CloseHandle(self.0) } + } + } +} + +impl Deref for OwnedHandle { + type Target = HANDLE; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OwnedHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +// There can be many different types that need to be LocalFree'd. PWSTR, PCWSTR, PSTR, PCSTR, PSECURITY_DESCRIPTOR +// are all distinct types, but they are compatible with the windows::core::IntoParam trait. +// There's also *mut ACL though which is also LocalAlloc'd and that's the problem (probably not the last of its kind). +// Writing a wrapper trait that is implemented for both IntoParam (or its friends) and *const/mut T +// doesn't work due to E0119. Implementing our own trait for each concrete type is highly annoying and verbose. +// So now this calls transmute_copy and zeroed. It's ugly and somewhat unsafe, but it's simple and short. +#[repr(transparent)] +pub struct OwnedLocalAlloc(pub T); + +impl Default for OwnedLocalAlloc { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +impl Drop for OwnedLocalAlloc { + fn drop(&mut self) { + unsafe { + let ptr: HLOCAL = std::mem::transmute_copy(self); + if !ptr.0.is_null() { + LocalFree(ptr); + } + } + } +} + +impl Deref for OwnedLocalAlloc { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OwnedLocalAlloc { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +pub unsafe extern "system" fn ignore_ctrl_c(ctrl_type: u32) -> BOOL { + match ctrl_type { + CTRL_C_EVENT | CTRL_BREAK_EVENT => TRUE, + _ => FALSE, + } +} + +pub fn generate_rpc_endpoint_name(pid: u32) -> String { + format!(r"sudo_elevate_{pid}") +} + +pub fn is_running_elevated() -> Result { + // TODO! + // Do the thing Terminal does to see if UAC is entirely disabled: + // Which is basically (from Utils::CanUwpDragDrop) + // const auto elevationType = wil::get_token_information(processToken.get()); + // const auto elevationState = wil::get_token_information(processToken.get()); + // if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated) + // + + let current_token = current_process_token()?; + let elevation: TOKEN_ELEVATION = get_token_info(*current_token)?; + Ok(elevation.TokenIsElevated == 1) +} + +fn current_process_token() -> Result { + let mut token: OwnedHandle = Default::default(); + unsafe { + OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut *token)?; + } + Ok(token) +} +fn get_process_token(process: HANDLE) -> Result { + let mut token: OwnedHandle = Default::default(); + unsafe { + OpenProcessToken(process, TOKEN_QUERY, &mut *token)?; + } + Ok(token) +} + +// helper trait to get the TOKEN_INFORMATION_CLASS for a given type +trait TokenInfo { + fn info_class() -> TOKEN_INFORMATION_CLASS; +} +impl TokenInfo for TOKEN_ELEVATION_TYPE { + fn info_class() -> TOKEN_INFORMATION_CLASS { + TokenElevationType + } +} +impl TokenInfo for TOKEN_ELEVATION { + fn info_class() -> TOKEN_INFORMATION_CLASS { + TokenElevation + } +} +impl TokenInfo for SE_TOKEN_USER { + fn info_class() -> TOKEN_INFORMATION_CLASS { + TokenUser + } +} + +fn get_token_info(token: HANDLE) -> Result { + unsafe { + let mut info: T = std::mem::zeroed(); + let size = std::mem::size_of::() as u32; + let mut ret_size = size; + GetTokenInformation( + token, + T::info_class(), + Some(&mut info as *mut _ as _), + size, + &mut ret_size, + )?; + Ok(info) + } +} + +pub fn can_current_user_elevate() -> Result { + let current_token = current_process_token()?; + let elevation_type: TOKEN_ELEVATION_TYPE = get_token_info(*current_token)?; + Ok(elevation_type == TokenElevationTypeFull || elevation_type == TokenElevationTypeLimited) +} + +pub fn get_sid_for_process(process: HANDLE) -> Result { + let process_token = get_process_token(process)?; + let token_user: SE_TOKEN_USER = get_token_info(*process_token)?; + Ok(token_user.Anonymous2) +} + +pub fn get_current_user() -> Result { + unsafe { + let user = get_sid_for_process(GetCurrentProcess())?; + + let mut str_sid = OwnedLocalAlloc::default(); + ConvertSidToStringSidW(PSID(&user.Sid as *const _ as _), &mut *str_sid)?; + + str_sid.to_hstring() + } +} + +pub fn is_cmd_intrinsic(application: &str) -> bool { + // List from https://ss64.com/nt/syntax-internal.html + // + // The following are internal commands to cmd.exe + // ASSOC, BREAK, CALL ,CD/CHDIR, CLS, COLOR, COPY, DATE, DEL, DIR, DPATH, + // ECHO, ENDLOCAL, ERASE, EXIT, FOR, FTYPE, GOTO, IF, KEYS, MD/MKDIR, + // MKLINK (vista and above), MOVE, PATH, PAUSE, POPD, PROMPT, PUSHD, REM, + // REN/RENAME, RD/RMDIR, SET, SETLOCAL, SHIFT, START, TIME, TITLE, TYPE, + // VER, VERIFY, VOL + + // if the application is one of these, we need to do something special + // to make sure it works. + // + // We also want to makke sure it's case insensitive + matches!( + application.to_uppercase().as_str(), + "ASSOC" + | "BREAK" + | "CALL" + | "CD" + | "CHDIR" + | "CLS" + | "COLOR" + | "COPY" + | "DATE" + | "DEL" + | "DIR" + | "DPATH" + | "ECHO" + | "ENDLOCAL" + | "ERASE" + | "EXIT" + | "FOR" + | "FTYPE" + | "GOTO" + | "IF" + | "KEYS" + | "MD" + | "MKDIR" + | "MKLINK" + | "MOVE" + | "PATH" + | "PAUSE" + | "POPD" + | "PROMPT" + | "PUSHD" + | "REM" + | "REN" + | "RENAME" + | "RD" + | "RMDIR" + | "SET" + | "SETLOCAL" + | "SHIFT" + | "START" + | "TIME" + | "TITLE" + | "TYPE" + | "VER" + | "VERIFY" + | "VOL" + ) +} + +/// Returns the current environment as a null-delimited string. +pub fn env_as_string() -> String { + unsafe { + let beg = GetEnvironmentStringsW().0 as *const _; + let mut end = beg; + + // Try to figure out the end of the double-null terminated env block. + loop { + let len = wcslen(PCWSTR(end)); + if len == 0 { + break; + } + end = end.add(len + 1); + } + + // The string we want to return should not be double-null terminated. + // The last iteration above however added `len + 1` and so we need to undo that now. + // We use `saturating_sub` because at least theoretically `beg` may be an empty string. + let len = usize::try_from(end.offset_from(beg)) + .unwrap() + .saturating_sub(1); + let str = String::from_utf16_lossy(from_raw_parts(beg, len)); + let _ = FreeEnvironmentStringsW(PCWSTR(beg)); + str + } +} + +/// Splits a null-delimited environment string into key/value pairs. +pub fn env_from_raw_bytes(env_string: &str) -> impl Iterator { + env_string.split('\0').filter_map(|s| { + // In the early days the cmd.exe devs added env variables that start with "=". + // They look like "=C:=C:\foo\bar" and are used to track per-drive CWDs across cmd child-processes. + // See here for more information: https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + // The get() call simultaneously takes care of rejecting empty strings, which is neat. + let idx = s.get(1..)?.find('=')?; + // The `.get(1..)` call will slice off 1 character from the start of the string and thus from the `idx` value. + // This means that when we want to split the string into two parts `[0,idx)` and `(idx,length)` + // (= `[idx+1,length)` = without the "=" character) then we need to add +1 to both sides now. + Some((OsStr::new(&s[..idx + 1]), OsStr::new(&s[idx + 2..]))) + }) +} + +/// Windows does not actually support distinct command line parameters. They're all just given as a single string. +/// We can't just use `.join(" ")` either, because this breaks arguments with whitespaces. This function handles these details. +pub fn join_args>(args: &[T]) -> String { + // We estimate 3*args.len() overhead per arg: 2 quotes and 1 whitespace. + let expected_len = args + .len() + .checked_mul(3) + .and_then(|n| { + args.iter() + .map(|s| s.as_ref().len()) + .try_fold(n, usize::checked_add) + }) + .unwrap(); + + let mut accumulator = Vec::with_capacity(expected_len); + + // Fun fact: At the time of writing, Windows Terminal has a function called `QuoteAndEscapeCommandlineArg` + // and Rust's `std::sys::windows::args` crate has a `append_arg` function. Both functions are pretty much + // 1:1 identical to the code below, but both were written independently. I guess there aren't too many + // ways to express this concept, but I'm still somewhat surprised there aren't multiple ways to do it. + for (idx, arg) in args.iter().enumerate() { + if idx != 0 { + accumulator.push(b' '); + } + + let str = arg.as_ref(); + let quote = str.is_empty() || str.contains(' ') || str.contains('\t'); + if quote { + accumulator.push(b'"'); + } + + let mut backslashes: usize = 0; + for &x in str.as_bytes() { + if x == b'\\' { + backslashes += 1; + } else { + if x == b'"' { + accumulator.extend((0..=backslashes).map(|_| b'\\')); + } + backslashes = 0; + } + accumulator.push(x); + } + + if quote { + accumulator.extend((0..backslashes).map(|_| b'\\')); + accumulator.push(b'"'); + } + } + + // Assuming that our `args` slice was UTF8 the accumulator can't suddenly contain non-UTF8. + unsafe { String::from_utf8_unchecked(accumulator) } +} + +/// Joins a list of strings into a single string, each of which is null-terminated (including the final one). +pub fn pack_string_list_for_rpc>(args: &[T]) -> String { + let expected_len = args + .iter() + .map(|s| s.as_ref().len()) + // We extend each arg in args with 1 character: \0. + // This results in an added overhead of args.len() characters, which we + // implicitly add by setting it as the initial value for the fold(). + .try_fold(args.len(), usize::checked_add) + .unwrap(); + + let mut accumulator = String::with_capacity(expected_len); + for arg in args { + accumulator.push_str(arg.as_ref()); + accumulator.push('\0'); + } + accumulator +} + +/// Splits a string generated by `pack_args` up again. +pub fn unpack_string_list_from_rpc(args: Utf8Str) -> Result> { + Ok(args + .as_str()? + .split_terminator('\0') + .map(String::from) + .collect()) +} + +pub trait ConfigProvider { + fn get_setting_mode(&self) -> Result; + fn get_policy_mode(&self) -> Result; +} + +#[derive(Default)] +pub struct RegistryConfigProvider; +impl ConfigProvider for RegistryConfigProvider { + fn get_setting_mode(&self) -> Result { + windows_registry::LOCAL_MACHINE + .open("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Sudo") + .and_then(|key| key.get_u32("Enabled")) + } + fn get_policy_mode(&self) -> Result { + windows_registry::LOCAL_MACHINE + .open("SOFTWARE\\Policies\\Microsoft\\Windows\\Sudo") + .and_then(|key| key.get_u32("Enabled")) + } +} + +/// Get the current mode allowed by policy. +/// * If the policy isn't set (we fail to read the reg key), we'll return Ok(3) +/// (to indicate that all modes up to inline are allowed). +/// * If the policy is set, we'll return the value from the policy. +/// * If we fail to read the policy for any other reason, we'll return an error +/// (which should be treated as "disabled by policy") +pub fn get_allowed_mode_from_policy(config: &impl ConfigProvider) -> Result { + match config.get_policy_mode() { + Ok(v) => v.min(3).try_into(), + // This is okay! No policy state really means that it's _not_ disabled by policy. + Err(e) if e.code() == E_FILENOTFOUND => Ok(SudoMode::Normal), + Err(e) => Err(e), + } +} + +/// Get the current setting mode from the registry. If the setting isn't there, +/// we're disabled. If we fail to read the setting, we're disabled. Errors +/// should be treated as disabled. +pub fn get_setting_mode(config: &impl ConfigProvider) -> Result { + match config.get_setting_mode() { + Ok(v) => v.min(3).try_into(), + Err(e) if e.code() == E_FILENOTFOUND => Ok(SudoMode::Disabled), + Err(e) => Err(e), + } +} + +pub fn get_allowed_mode(config: &impl ConfigProvider) -> Result { + let allowed_mode_from_policy = get_allowed_mode_from_policy(config)?; + if allowed_mode_from_policy == SudoMode::Disabled { + return Err(E_ACCESS_DISABLED_BY_POLICY.into()); + } + let setting_mode = get_setting_mode(config).unwrap_or(SudoMode::Disabled); + + SudoMode::try_from(std::cmp::min::( + allowed_mode_from_policy.into(), + setting_mode.into(), + )) +} + +pub fn get_process_path_from_handle(process: HANDLE) -> Result { + let mut buffer = vec![0u16; MAX_PATH as usize]; + + // Call QueryFullProcessImageNameW in a loop to make sure we actually get the + // full path. We have to do it in a loop, because QueryFullProcessImageNameW + // doesn't actually tell us how big the buffer needs to be on error. + loop { + let mut len = buffer.len() as u32; + match unsafe { + QueryFullProcessImageNameW( + process, + Default::default(), + PWSTR(buffer.as_mut_ptr()), + &mut len, + ) + } { + Ok(()) => return Ok(PathBuf::from(OsString::from_wide(&buffer[..len as usize]))), + Err(err) if err.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() => return Err(err), + Err(_) => buffer.resize(buffer.len() * 2, 0), + }; + } +} + +/// Check that the client process is the same as the server process. +pub fn check_client(client_handle: HANDLE) -> Result<()> { + // Open a handle to the provided process + let process_path = get_process_path_from_handle(client_handle)?; + let our_path = std::env::current_exe().unwrap(); + trace_log_message(&format!( + "{process_path:?} connected to server {our_path:?}" + )); + + // Now, is this path the same as us? (ignoring case) + if !process_path + .as_os_str() + .eq_ignore_ascii_case(our_path.as_os_str()) + { + return Err(E_ACCESSDENIED.into()); + } + + let mut client_sid = get_sid_for_process(client_handle)?; + let mut our_sid = unsafe { get_sid_for_process(GetCurrentProcess())? }; + // Are these SIDs the same? This check prevents over-the-shoulder elevation + // when the RPC server is in use. + unsafe { + // If the SID structures are equal, the return value is nonzero (TRUE) + // Then the windows-rs projection will take the true and convert that to Ok(()), or FALSE to Err(GetLastError()) + // EqualSid(PSID{ 0: &mut client_sid.Sid as *mut _ } , &mut our_sid.Sid as *mut _ as PSID)?; + let client_psid: PSID = PSID(&mut client_sid.Buffer as *mut _ as _); + let our_psid: PSID = PSID(&mut our_sid.Buffer as *mut _ as _); + EqualSid(client_psid, our_psid)?; + }; + + Ok(()) +} + +/// Make a Windows path absolute, using GetFullPathNameW to resolve the file on disk. +/// Largely lifted from the rust stdlib, because it's _currently_ a nightly-only function. +/// We don't have all the same internal stdlib helpers they do, but it's effectively the same.. +pub fn absolute_path(path: &Path) -> Result { + if path.as_os_str().as_encoded_bytes().starts_with(br"\\?\") { + return Ok(path.into()); + } + let lpfilename = HSTRING::from(path.as_os_str()); + let mut buffer = vec![0u16; MAX_PATH as usize]; + loop { + // GetFullPathNameW will return the required buffer size if the buffer is too small. + let res = unsafe { GetFullPathNameW(&lpfilename, Some(buffer.as_mut_slice()), None) }; + match res as usize { + 0 => return Err(Error::from_win32()), // returns GLE + len if len <= buffer.len() => { + return Ok(PathBuf::from(OsString::from_wide(&buffer[..len]))) + } + new_len => buffer.resize(new_len, 0), + } + } +} + +unsafe fn read_struct_at(f: &mut File, offset: u64) -> Result { + let mut data = MaybeUninit::::uninit(); + let bytes = from_raw_parts_mut(data.as_mut_ptr() as *mut u8, size_of::()); + let read = f.seek_read(bytes, offset)?; + if read != bytes.len() { + return Err(ERROR_HANDLE_EOF.into()); + } + Ok(data.assume_init()) +} + +pub fn get_exe_subsystem>(path: P) -> Result { + let mut f = File::open(path)?; + + let dos: IMAGE_DOS_HEADER = unsafe { read_struct_at(&mut f, 0)? }; + if dos.e_magic != IMAGE_DOS_SIGNATURE { + return Err(ERROR_BAD_EXE_FORMAT.into()); + } + + // IMAGE_NT_HEADERS32 and IMAGE_NT_HEADERS64 have different sizes, + // but the offset of the .OptionalHeader.Subsystem member is identical. + let nt: IMAGE_NT_HEADERS32 = unsafe { read_struct_at(&mut f, dos.e_lfanew as u64)? }; + if nt.Signature != IMAGE_NT_SIGNATURE { + return Err(ERROR_BAD_EXE_FORMAT.into()); + } + + Ok(nt.OptionalHeader.Subsystem) +} + +#[cfg(test)] +mod tests { + use super::*; + use windows::Win32::System::Diagnostics::Debug::{ + IMAGE_SUBSYSTEM_WINDOWS_CUI, IMAGE_SUBSYSTEM_WINDOWS_GUI, + }; + + #[test] + fn test_env_from_raw_string() { + let raw_string = "foo=bar\0baz=qux\0\0"; + let env_map: Vec<_> = env_from_raw_bytes(raw_string).collect(); + assert_eq!(env_map.len(), 2); + assert_eq!(env_map[0], (OsStr::new("foo"), OsStr::new("bar"))); + assert_eq!(env_map[1], (OsStr::new("baz"), OsStr::new("qux"))); + } + + #[test] + fn test_env_with_drive_vars() { + let raw_string = "foo=bar\0=D:=D:\\qux\0\0"; + let env_map: Vec<_> = env_from_raw_bytes(raw_string).collect(); + assert_eq!(env_map.len(), 2); + assert_eq!(env_map[0], (OsStr::new("foo"), OsStr::new("bar"))); + assert_eq!(env_map[1], (OsStr::new("=D:"), OsStr::new("D:\\qux"))); + } + + #[test] + fn test_join_args() { + assert_eq!(join_args(&[""; 0]), ""); + assert_eq!(join_args(&["foo", "bar"]), "foo bar"); + assert_eq!(join_args(&["f \too", " bar\t"]), "\"f \too\" \" bar\t\""); + assert_eq!(join_args(&["f\\\"oo", "\"bar\""]), r#"f\\\"oo \"bar\""#); + } + + #[test] + fn test_pack_args() { + assert_eq!(pack_string_list_for_rpc(&[""; 0]), ""); + assert_eq!(pack_string_list_for_rpc(&["foo"]), "foo\0"); + assert_eq!(pack_string_list_for_rpc(&["foo", "bar"]), "foo\0bar\0"); + } + + #[test] + fn test_unpack_args() { + assert_eq!(unpack_string_list_from_rpc("".into()).unwrap(), [""; 0]); + assert_eq!(unpack_string_list_from_rpc("foo".into()).unwrap(), ["foo"]); + assert_eq!( + unpack_string_list_from_rpc("foo\0".into()).unwrap(), + ["foo"] + ); + assert_eq!( + unpack_string_list_from_rpc("foo\0bar".into()).unwrap(), + ["foo", "bar"] + ); + assert_eq!( + unpack_string_list_from_rpc("foo\0bar\0".into()).unwrap(), + ["foo", "bar"] + ); + assert_eq!( + unpack_string_list_from_rpc("\0\0".into()).unwrap(), + ["", ""] + ); + assert_eq!( + unpack_string_list_from_rpc("foo\0\0bar\0\0".into()).unwrap(), + ["foo", "", "bar", ""] + ); + } + + #[test] + fn test_get_exe_subsystem() { + assert_eq!( + Ok(IMAGE_SUBSYSTEM_WINDOWS_CUI), + get_exe_subsystem(r"C:\Windows\System32\nslookup.exe") + ); + assert_eq!( + Ok(IMAGE_SUBSYSTEM_WINDOWS_GUI), + get_exe_subsystem(r"C:\Windows\notepad.exe") + ); + } + + /// config tests + struct TestConfigProvider { + setting_mode: Result, + policy_mode: Result, + } + + impl ConfigProvider for TestConfigProvider { + fn get_setting_mode(&self) -> Result { + self.setting_mode.clone() + } + fn get_policy_mode(&self) -> Result { + self.policy_mode.clone() + } + } + + #[test] + fn test_get_allowed_mode_from_policy() { + // no setting at all + let config = TestConfigProvider { + setting_mode: Err(E_FILENOTFOUND.into()), + policy_mode: Err(E_FILENOTFOUND.into()), + }; + assert_eq!(get_setting_mode(&config).unwrap(), SudoMode::Disabled); + assert_eq!( + get_allowed_mode_from_policy(&config).unwrap(), + SudoMode::Normal + ); + assert_eq!(get_allowed_mode(&config).unwrap(), SudoMode::Disabled); + + // Setting set to 3 (normal), but policy only allows 2 (disable input) + let config = TestConfigProvider { + setting_mode: Ok(3), + policy_mode: Ok(2), + }; + assert_eq!(get_setting_mode(&config).unwrap(), SudoMode::Normal); + assert_eq!( + get_allowed_mode_from_policy(&config).unwrap(), + SudoMode::DisableInput + ); + assert_eq!(get_allowed_mode(&config).unwrap(), SudoMode::DisableInput); + + // policy is out of range + let config = TestConfigProvider { + setting_mode: Ok(3), + policy_mode: Ok(4), + }; + assert_eq!(get_setting_mode(&config).unwrap(), SudoMode::Normal); + assert_eq!( + get_allowed_mode_from_policy(&config).unwrap(), + SudoMode::Normal + ); + assert_eq!(get_allowed_mode(&config).unwrap(), SudoMode::Normal); + + // entirely disabled by policy + let config = TestConfigProvider { + setting_mode: Ok(3), + policy_mode: Ok(0), + }; + assert_eq!(get_setting_mode(&config).unwrap(), SudoMode::Normal); + assert_eq!( + get_allowed_mode_from_policy(&config).unwrap(), + SudoMode::Disabled + ); + assert_eq!( + get_allowed_mode(&config), + Err(E_ACCESS_DISABLED_BY_POLICY.into()) + ); + + // No policy config found + let config = TestConfigProvider { + setting_mode: Ok(3), + policy_mode: Err(E_FILENOTFOUND.into()), + }; + assert_eq!(get_setting_mode(&config).unwrap(), SudoMode::Normal); + assert_eq!( + get_allowed_mode_from_policy(&config).unwrap(), + SudoMode::Normal + ); + assert_eq!(get_allowed_mode(&config).unwrap(), SudoMode::Normal); + + // not set, but disabled by policy + let config = TestConfigProvider { + setting_mode: Err(E_FILENOTFOUND.into()), + policy_mode: Ok(0), + }; + assert_eq!(get_setting_mode(&config).unwrap(), SudoMode::Disabled); + assert_eq!( + get_allowed_mode_from_policy(&config).unwrap(), + SudoMode::Disabled + ); + assert_eq!( + get_allowed_mode(&config), + Err(E_ACCESS_DISABLED_BY_POLICY.into()) + ); + } +} diff --git a/sudo/src/logging_bindings.rs b/sudo/src/logging_bindings.rs new file mode 100644 index 0000000..c40346a --- /dev/null +++ b/sudo/src/logging_bindings.rs @@ -0,0 +1,146 @@ +use crate::helpers::join_args; +use crate::messages::ElevateRequest; +use std::env; +use std::ffi::CString; +use std::mem::size_of_val; +use std::ops::{Deref, DerefMut}; +use std::ptr::addr_of; +use windows::core::*; +use windows::Win32::System::Diagnostics::Etw::*; + +// These come from cpp/logging/EventViewerLogging.c +extern "C" { + static PROVIDER_GUID: GUID; + static SudoRequestRunEvent: EVENT_DESCRIPTOR; + static SudoRecieveRunRequestEvent: EVENT_DESCRIPTOR; +} + +#[repr(transparent)] +#[derive(Default)] +struct OwnedReghandle(pub u64); + +impl Drop for OwnedReghandle { + fn drop(&mut self) { + if self.0 != 0 { + unsafe { + EventUnregister(self.0); + } + } + } +} + +impl Deref for OwnedReghandle { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OwnedReghandle { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +fn str_to_cstr_vec>>(s: T) -> Vec { + CString::new(s) + .expect("strings should not have nulls") + .into_bytes_with_nul() +} + +fn create_descriptor(ptr: *const T, len: U) -> EVENT_DATA_DESCRIPTOR +where + U: TryInto, + >::Error: std::fmt::Debug, +{ + EVENT_DATA_DESCRIPTOR { + Ptr: ptr as _, + Size: len.try_into().unwrap(), + Anonymous: Default::default(), + } +} + +/// Writes this request to the Windows Event Log. We do this for admins to be +/// able to audit who's calling what with sudo. +/// We log our messages to "Applications and Services Logs" -> "Microsoft" -> +/// "Windows" -> "Sudo" -> "Admin". +/// +/// Alternatively, you can view this log with +/// `wevtutil qe Microsoft-Windows-Sudo/Admin /c:3 /rd:true /f:text` +pub fn event_log_request(is_client: bool, req: &ElevateRequest) { + let mut registration_handle = OwnedReghandle::default(); + // The error code returned by EventRegister is primarily intended for use in debugging and diagnostic scenarios. + // Most production code should continue to run even if an ETW provider failed to register, + // so release builds should usually ignore the error code returned by EventRegister. + unsafe { EventRegister(&PROVIDER_GUID, None, None, &mut *registration_handle) }; + + let application = str_to_cstr_vec(req.application.as_str()); + let args_len = req.args.len() as u32; + let args: Vec<_> = req + .args + .iter() + .map(|arg| str_to_cstr_vec(arg.as_str())) + .collect(); + let cwd = str_to_cstr_vec(req.target_dir.as_str()); + let mode = req.sudo_mode as u32; + let inherit_env = !req.env_vars.is_empty(); + let redirected = req.handles.iter().any(|h| !h.is_invalid()); + let commandline = str_to_cstr_vec(format!( + "{} {} {}", + env::current_exe().unwrap().display(), + req.application, + join_args(&req.args) + )); + let request_id = req.event_id; + + let mut descriptors = Vec::with_capacity(9 + args.len()); + // + descriptors.push(create_descriptor(application.as_ptr(), application.len())); + // + descriptors.push(create_descriptor( + addr_of!(args_len), + size_of_val(&args_len), + )); + // + for arg in &args { + descriptors.push(EVENT_DATA_DESCRIPTOR { + Ptr: arg.as_ptr() as _, + Size: arg.len() as u32, + Anonymous: Default::default(), + }); + } + // + descriptors.push(create_descriptor(cwd.as_ptr(), cwd.len())); + // + descriptors.push(create_descriptor(addr_of!(mode), size_of_val(&mode))); + // + descriptors.push(create_descriptor( + addr_of!(inherit_env), + size_of_val(&inherit_env), + )); + // + descriptors.push(create_descriptor( + addr_of!(redirected), + size_of_val(&redirected), + )); + // + descriptors.push(create_descriptor(commandline.as_ptr(), commandline.len())); + // + descriptors.push(create_descriptor( + addr_of!(request_id), + size_of_val(&request_id), + )); + + unsafe { + // We're literally using the same data template for both requests and + // responses. The only difference is the event ID's have different keywords + // (to ID who sent the event). + let event_id = if is_client { + &SudoRequestRunEvent + } else { + &SudoRecieveRunRequestEvent + }; + EventWrite(*registration_handle, event_id, Some(&descriptors)); + } +} diff --git a/sudo/src/main.rs b/sudo/src/main.rs new file mode 100644 index 0000000..2dda197 --- /dev/null +++ b/sudo/src/main.rs @@ -0,0 +1,461 @@ +mod elevate_handler; +mod helpers; +mod logging_bindings; +mod messages; +mod r; +mod rpc_bindings; +mod rpc_bindings_client; +mod rpc_bindings_server; +mod run_handler; +mod tests; +mod tracing; + +use clap::{Arg, ArgAction, ArgMatches, Command}; +use elevate_handler::start_rpc_server; +use helpers::*; +use run_handler::run_target; +use std::env; +use tracing::*; +use windows::{core::*, Win32::Foundation::*, Win32::System::Console::*}; + +// Clap does provide a nice macro for args, which defines args with a snytax +// close to what the actual help text would be. Unfortunately, we're not using +// that macro, because it doesn't play well with localization. The comments +// throughout here help show how the macro would have worked. +fn sudo_cli(allowed_mode: i32) -> Command { + const POLICY_DENIED_LABEL: i32 = E_ACCESS_DISABLED_BY_POLICY.0; + + let mut app = Command::new(env!("CARGO_CRATE_NAME")); + match allowed_mode { + 0 => { + // In this case, our error message has some VT in it, so we need to + // turn VT on before we eventually print the message. Fortunately, + // the `check_enabled_or_bail` will make sure to enable & restore VT + // mode before printing that error. + + // Sudo is disabled. We want to inform them when they see the help text. + app = app + .about(r::IDS_SUDONAME.get()) + .long_about(r::IDS_DISABLEDLONGABOUT.get()) + .override_help(r::IDS_DISABLEDLONGABOUT.get()); + } + POLICY_DENIED_LABEL => { + // Sudo is disabled by policy. The help text will be more specific. + app = app + .about(r::IDS_SUDONAME.get()) + .long_about(r::IDS_DISABLEDBYPOLICY.get()) + .override_help(r::IDS_DISABLEDBYPOLICY.get()); + } + _ => { + // Sudo is enabled. The help text will be standard. + app = app + .about(r::IDS_SUDONAME.get()) + .long_about(r::IDS_LONGABOUT.get()); + } + } + + app = app + .subcommand_required(false) + .arg_required_else_help(true) + .allow_external_subcommands(true) + .version(env!("CARGO_PKG_VERSION")) + .args(run_args()) + .subcommand(run_builder()) + .subcommand( + // The elevate command is hidden, and not documented in the help text. + Command::new("elevate") + .about(r::IDS_ELEVATE_ABOUT.get()) + .hide(true) + .disable_help_flag(true) + // .arg(arg!(-p "Parent process ID").required(true)) + .arg( + Arg::new("PARENT") + .short('p') + .help(r::IDS_ELEVATE_PARENT.get()) + .required(true), + ) + // .arg(arg!([COMMANDLINE] ... "")), + .arg( + Arg::new("COMMANDLINE") + .help(r::IDS_ELEVATE_COMMANDLINE.get()) + .action(ArgAction::Append) + .trailing_var_arg(true), + ), + ) + .disable_help_flag(true) + .disable_version_flag(true) + .arg( + Arg::new("help") + .action(ArgAction::Help) + .short('h') + .long("help") + .help(r::IDS_BASE_HELP_HELP_SHORT.get()) + .long_help(r::IDS_BASE_HELP_HELP_LONG.get()), + ) + .arg( + Arg::new("version") + .action(ArgAction::Version) + .short('V') + .long("version") + .help(r::IDS_BASE_VERSION_HELP.get()), + ); + let config = Command::new("config").about(r::IDS_CONFIG_ABOUT.get()).arg( + Arg::new("enable") + .long("enable") + .value_parser([ + "disable", + "enable", + "forceNewWindow", + "disableInput", + "normal", + "default", + ]) + .default_missing_value_os("default") + .required(false) + .action(ArgAction::Set), + ); + app = app.subcommand(config); + + app +} + +fn run_builder() -> Command { + Command::new("run") + .about(r::IDS_RUN_ABOUT.get()) + .arg_required_else_help(true) + .args(run_args()) +} + +fn run_args() -> Vec { + // trailing_var_arg and allow_hyphen_values are needed to allow passing in a + // command like `sudo netstat -ab` to work as expected, instead of having + // the parser attempt to treat the `-ab` as args to sudo itself. + let args = vec![ + // arg!(-E --"preserve-env" "pass the current environment variables to the command") + Arg::new("copyEnv") + .short('E') + .long("preserve-env") + .help(r::IDS_RUN_COPYENV_HELP.get()) + .action(ArgAction::SetTrue), + // arg!(-N --"new-window" "Use a new window for the command.") + Arg::new("newWindow") + .short('N') + .long("new-window") + .help(r::IDS_RUN_NEWWINDOW_HELP.get()) + .action(ArgAction::SetTrue) + .group("mode"), + // arg!(--"disable-input" "Disable input to the target application") + Arg::new("disableInput") + .long("disable-input") + .help(r::IDS_RUN_DISABLEINPUT_HELP.get()) + .action(ArgAction::SetTrue) + .group("mode"), + // arg!(--"inline" "Run in the current terminal") + Arg::new("inline") + .long("inline") + .help(r::IDS_RUN_INLINE_HELP.get()) + .action(ArgAction::SetTrue) + .group("mode"), + // arg!(--"chdir"=

"Change the working directory to DIR before running the command.") + Arg::new("chdir") + .short('D') + .long("chdir") + .help(r::IDS_RUN_CHDIR_HELP.get()) + .action(ArgAction::Set), + // arg!([COMMANDLINE] ... "Command-line to run") + Arg::new("COMMANDLINE") + .help(r::IDS_RUN_COMMANDLINE_HELP.get()) + .action(ArgAction::Append) + .trailing_var_arg(true), + ]; + + // The following is a demo of how feature flags might work in the sudo + // codebase. You can add a `Feature_test_flag` feature to the Dev branding + // in cargo.toml, and then add conditionally enabled code, like so: + // + // if cfg!(feature = "Feature_test_flag") { + // args.append(&mut vec![Arg::new("setHome") + // .short('H') + // .long("set-home") + // .help(r::IDS_RUN_SETHOME_HELP.get()) + // .action(ArgAction::SetTrue)]); + // } + args +} + +fn log_modes(requested_mode: Option) { + let config: RegistryConfigProvider = Default::default(); + let setting_mode = get_setting_mode(&config).unwrap_or(SudoMode::Disabled) as u32; + let policy_mode = { + let policy_enabled = windows_registry::LOCAL_MACHINE + .open("SOFTWARE\\Policies\\Microsoft\\Windows\\Sudo") + .and_then(|key| key.get_u32("Enabled")); + if let Err(e) = &policy_enabled { + if e.code() == E_FILENOTFOUND { + 0xffffffff + } else { + 0 + } + } else { + policy_enabled.unwrap_or(0) + } + }; + // Trace "disabled" as "they didn't pass a mode manually". + trace_modes( + requested_mode.unwrap_or(SudoMode::Disabled) as u32, + setting_mode, + policy_mode, + ); +} + +fn check_enabled_or_bail() -> SudoMode { + let config: RegistryConfigProvider = Default::default(); + // First things first: Make sure we're enabled. + match get_allowed_mode(&config) { + Err(e) => { + if e.code() == E_ACCESSDENIED { + // Any time you want to use IDS_DISABLEDLONGABOUT, make sure you turned on VT first + let mode = enable_vt(); + eprintln!("{}", r::IDS_DISABLEDLONGABOUT.get()); + _ = restore_console_mode(mode); + } else if e.code() == ERROR_ACCESS_DISABLED_BY_POLICY.into() { + eprintln!("{}", r::IDS_DISABLEDBYPOLICY.get()); + } else { + eprintln!("{} {}", r::IDS_UNKNOWNERROR.get(), e); + } + std::process::exit(e.code().0); + } + Ok(SudoMode::Disabled) => { + // Any time you want to use IDS_DISABLEDLONGABOUT, make sure you turned on VT first + let mode = enable_vt(); + eprintln!("{}", r::IDS_DISABLEDLONGABOUT.get()); + _ = restore_console_mode(mode); + std::process::exit(E_ACCESSDENIED.0); + } + Ok(mode) => mode, + } +} + +/// We want to be able to conditionally control what the help text shows, +/// depending on if sudo is enabled, disabled, or disabled by policy. This +/// helper lets us do that more easily. This will return: +/// * 0 if sudo is disabled +/// * E_ACCESS_DISABLED_BY_POLICY if sudo is disabled by policy +/// * or the current mode (>0), if sudo is enabled. +fn allowed_mode_for_help() -> i32 { + let config: RegistryConfigProvider = Default::default(); + match get_allowed_mode(&config) { + Err(e) => { + if e.code() == E_ACCESSDENIED { + 0 + } else if e.code() == E_ACCESS_DISABLED_BY_POLICY { + E_ACCESS_DISABLED_BY_POLICY.0 + } else { + 0 + } + } + Ok(SudoMode::Disabled) => 0, + Ok(mode) => mode.into(), + } +} +/// Try to enable VT processing in the console, but also ignore any errors. +fn enable_vt() -> CONSOLE_MODE { + enable_vt_or_err().unwrap_or_default() +} + +fn enable_vt_or_err() -> Result { + unsafe { + let mut console_mode = CONSOLE_MODE::default(); + let console_handle = GetStdHandle(STD_OUTPUT_HANDLE)?; + GetConsoleMode(console_handle, &mut console_mode)?; + SetConsoleMode( + console_handle, + console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING, + )?; + Ok(console_mode) + } +} +fn restore_console_mode(mode: CONSOLE_MODE) -> Result<()> { + unsafe { + let console_handle = GetStdHandle(STD_OUTPUT_HANDLE)?; + SetConsoleMode(console_handle, mode)?; + } + Ok(()) +} + +fn main() { + enable_tracing(); + + trace_log_message(&format!("raw commandline: {:?}", env::args_os())); + let mode_for_help = allowed_mode_for_help(); + let matches = sudo_cli(mode_for_help).get_matches(); + + let res = match matches.subcommand() { + Some(("elevate", sub_matches)) => do_elevate(sub_matches), + Some(("run", sub_matches)) => do_run(sub_matches), + Some(("config", sub_matches)) => do_config(sub_matches), + _ => do_run(&matches), + }; + + let code = res.unwrap_or_else(|err| { + let hr = err.code(); + let mut code = hr.0; + match hr { + E_DIR_BAD_COMMAND_OR_FILE => { + eprintln!("{}", r::IDS_COMMANDNOTFOUND.get()); + code = MSG_DIR_BAD_COMMAND_OR_FILE.0 as i32; + } + E_CANCELLED => { + eprintln!("{}", r::IDS_CANCELLED.get()); + } + _ if hr == HRESULT::from_win32(ERROR_REQUEST_REFUSED.0) => { + eprintln!("{}", r::IDS_SUDO_DISALLOWED.get()); + } + _ => { + eprintln!("{} {}", r::IDS_UNKNOWNERROR.get(), err); + } + }; + code + }); + + // Normally this is where we'd construct an ExitCode and return it from main(), + // but it only supports u8 (...why?) and windows_process_exit_code_from is an unstable feature. + std::process::exit(code) +} + +fn do_run(matches: &ArgMatches) -> Result { + let commandline = matches + .get_many::("COMMANDLINE") + .into_iter() + .flatten() + .collect::>(); + + // Didn't pass a commandline or just "/?"? Print the help text and bail, BEFORE checking the mode. + if commandline.is_empty() || (commandline.len() == 1 && commandline[0] == "/?") { + _ = run_builder().print_long_help(); + // return exit status 1 if the commandline was empty, 0 otherwise + return Ok(commandline.is_empty().into()); + } + let requested_dir: Option = matches.get_one::("chdir").map(|s| s.into()); + let allowed_mode = check_enabled_or_bail(); + let copy_env = matches.get_flag("copyEnv"); + + if !can_current_user_elevate()? { + // Bail out with an error. main(0) will then print the error message to + // the user to let them know they aren't allowed to run sudo. + return Err(ERROR_REQUEST_REFUSED.into()); + } + + let requested_mode = if matches.get_flag("newWindow") { + Some(SudoMode::ForceNewWindow) + } else if matches.get_flag("disableInput") { + Some(SudoMode::DisableInput) + } else if matches.get_flag("inline") { + Some(SudoMode::Normal) + } else { + None + }; + + log_modes(requested_mode); + + if let Some(mode) = requested_mode { + if mode > allowed_mode { + match allowed_mode { + SudoMode::Disabled => {} // This is already handled by check_enabled_or_bail + SudoMode::ForceNewWindow => eprintln!("{}", r::IDS_MAXRUNMODE_FORCENEWWINDOW.get()), + SudoMode::DisableInput => eprintln!("{}", r::IDS_MAXRUNMODE_DISABLEINPUT.get()), + SudoMode::Normal => {} // not possible to exceed normal mode + } + std::process::exit(-1); + } + } + + let actual_mode = std::cmp::min(allowed_mode, requested_mode.unwrap_or(allowed_mode)); + + run_target(copy_env, &commandline, actual_mode, requested_dir) +} + +fn do_elevate(matches: &ArgMatches) -> Result { + _ = check_enabled_or_bail(); + + let parent_pid = matches.get_one::("PARENT").unwrap().parse::(); + + if let Err(e) = &parent_pid { + eprintln!("{} {}", r::IDS_UNKNOWNERROR.get(), e); + std::process::exit(-1); + } + trace_log_message(&format!("elevate starting for parent: {parent_pid:?}")); + + trace_log_message(&format!("matches: {matches:?}")); + + let commandline = matches + .get_many::("COMMANDLINE") + .into_iter() + .flatten() + .collect::>(); + + let result = start_rpc_server(parent_pid.ok().unwrap(), None, &commandline); + trace_log_message(&format!("elevate result: {result:?}")); + result +} + +fn do_config(matches: &ArgMatches) -> Result { + let mode = match matches.get_one::("enable") { + Some(mode) => { + let mode = match mode.as_str() { + "disable" => SudoMode::Disabled, + "enable" => SudoMode::Normal, + "forceNewWindow" => SudoMode::ForceNewWindow, + "disableInput" => SudoMode::DisableInput, + "normal" => SudoMode::Normal, + "default" => SudoMode::Normal, + _ => { + eprintln!("{} {}", r::IDS_INVALIDMODE.get(), mode); + std::process::exit(-1); + } + }; + try_enable_sudo(mode)?; + mode + } + None => check_enabled_or_bail(), + }; + + match mode { + SudoMode::Disabled => println!("{}", r::IDS_DISABLEDMESSAGE.get()), + SudoMode::ForceNewWindow => println!("{}", r::IDS_CURRENTMODE_FORCENEWWINDOW.get()), + SudoMode::DisableInput => println!("{}", r::IDS_CURRENTMODE_DISABLEINPUT.get()), + SudoMode::Normal => println!("{}", r::IDS_CURRENTMODE_INLINE.get()), + } + + Ok(0) +} + +fn try_enable_sudo(requested_mode: SudoMode) -> Result<()> { + let elevated = is_running_elevated()?; + if !elevated { + eprintln!("{}", r::IDS_REQUIREADMINTOCONFIG.get()); + std::process::exit(-1); + } + let config: RegistryConfigProvider = Default::default(); + let max_mode = get_allowed_mode_from_policy(&config)?; + if requested_mode > max_mode { + match max_mode { + SudoMode::Disabled => eprintln!("{}", r::IDS_DISABLEDBYPOLICY.get()), + SudoMode::ForceNewWindow => eprintln!("{}", r::IDS_MAXPOLICYMODE_FORCENEWWINDOW.get()), + SudoMode::DisableInput => eprintln!("{}", r::IDS_MAXPOLICYMODE_DISABLEINPUT.get()), + SudoMode::Normal => eprintln!("{}", r::IDS_MAXPOLICYMODE_INLINE.get()), + } + std::process::exit(-1); + } + + let result = windows_registry::LOCAL_MACHINE + .create("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Sudo") + .and_then(|key| key.set_u32("Enabled", requested_mode.into())); + + if let Err(err) = result { + eprintln!("{} {}", r::IDS_ERRORSETTINGMODE.get(), err); + return Err(err); + } + + Ok(()) +} diff --git a/sudo/src/messages.rs b/sudo/src/messages.rs new file mode 100644 index 0000000..e3fb078 --- /dev/null +++ b/sudo/src/messages.rs @@ -0,0 +1,13 @@ +use crate::helpers::SudoMode; +use windows::{core::GUID, Win32::Foundation::HANDLE}; + +pub struct ElevateRequest { + pub parent_pid: u32, + pub handles: [HANDLE; 3], // in, out, err + pub sudo_mode: SudoMode, + pub application: String, + pub args: Vec, + pub target_dir: String, + pub env_vars: String, + pub event_id: GUID, +} diff --git a/sudo/src/r.rs b/sudo/src/r.rs new file mode 100644 index 0000000..69117c7 --- /dev/null +++ b/sudo/src/r.rs @@ -0,0 +1,23 @@ +//! This file includes all our resource IDs, and the code to load them. The +//! handy string_resources macro does the magic to create a StaticStringResource +//! for each of the resource IDs, and then we can use them in code. +//! +//! Example usage: +//! let world = r::IDS_WORLD.get(); +//! println!("Hello: {}", world); + +#![allow(dead_code)] +use win32resources::StaticStringResource; +macro_rules! string_resources { + ( + $( + $name:ident = $value:expr ; + )* + ) => { + $( + pub static $name: StaticStringResource = StaticStringResource::new($value, stringify!($name)); + )* + } +} + +include!("../../Generated Files/resource_ids.rs"); diff --git a/sudo/src/rpc_bindings.rs b/sudo/src/rpc_bindings.rs new file mode 100644 index 0000000..59f75f7 --- /dev/null +++ b/sudo/src/rpc_bindings.rs @@ -0,0 +1,34 @@ +use std::cmp::min; +use std::marker::PhantomData; +use std::slice::from_raw_parts; +use std::str::from_utf8; +use windows::{core::*, Win32::Foundation::*}; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Utf8Str<'a> { + length: u32, + data: *const u8, + _marker: PhantomData<&'a str>, +} + +impl<'a> Utf8Str<'a> { + pub fn new(s: &str) -> Utf8Str { + Utf8Str { + length: min(s.len(), u32::MAX as usize) as u32, + data: s.as_ptr(), + _marker: PhantomData, + } + } + + pub fn as_str(&self) -> Result<&str> { + from_utf8(unsafe { from_raw_parts(self.data, self.length as usize) }) + .map_err(|_| ERROR_NO_UNICODE_TRANSLATION.to_hresult().into()) + } +} + +impl<'a> From<&'a str> for Utf8Str<'a> { + fn from(value: &'a str) -> Self { + Utf8Str::new(value) + } +} diff --git a/sudo/src/rpc_bindings_client.rs b/sudo/src/rpc_bindings_client.rs new file mode 100644 index 0000000..17b04c8 --- /dev/null +++ b/sudo/src/rpc_bindings_client.rs @@ -0,0 +1,109 @@ +use crate::helpers::SudoMode; +use crate::rpc_bindings::Utf8Str; +use std::ffi::{c_void, CStr}; +use windows::core::{s, GUID, HRESULT, PCSTR, PSTR}; +use windows::Win32::Foundation::HANDLE; +use windows::Win32::Storage::FileSystem::{GetFileType, FILE_TYPE_DISK, FILE_TYPE_PIPE}; +use windows::Win32::System::Rpc::{ + RpcBindingFree, RpcBindingFromStringBindingA, RpcMgmtIsServerListening, + RpcStringBindingComposeA, RpcStringFreeA, RPC_STATUS, RPC_S_OK, +}; + +extern "C" { + static mut client_sudo_rpc_ClientIfHandle: *mut c_void; + + fn seh_wrapper_client_DoElevationRequest( + binding: *mut c_void, + parent_handle: HANDLE, + pipe_handles: *const [HANDLE; 3], // in, out, err + file_handles: *const [HANDLE; 3], // in, out, err + sudo_mode: u32, + application: Utf8Str, + args: Utf8Str, + target_dir: Utf8Str, + env_vars: Utf8Str, + event_id: GUID, + child: *mut HANDLE, + ) -> HRESULT; + + fn seh_wrapper_client_Shutdown(binding: *mut c_void) -> HRESULT; +} + +pub fn rpc_client_setup(endpoint: &CStr) -> RPC_STATUS { + unsafe { + let mut string_binding = PSTR::null(); + let status = RpcStringBindingComposeA( + /* ObjUuid */ None, + /* ProtSeq */ s!("ncalrpc"), + /* NetworkAddr */ None, + /* Endpoint */ PCSTR(endpoint.as_ptr() as _), + /* Options */ None, + /* StringBinding */ Some(&mut string_binding), + ); + if status != RPC_S_OK { + return status; + } + + let status = RpcBindingFromStringBindingA( + PCSTR(string_binding.0), + std::ptr::addr_of_mut!(client_sudo_rpc_ClientIfHandle), + ); + // Don't forget to free the previously allocated string before potentially returning. :) + RpcStringFreeA(&mut string_binding); + if status != RPC_S_OK { + return status; + } + + RpcMgmtIsServerListening(Some(client_sudo_rpc_ClientIfHandle)) + } +} + +/// Cleans up the RPC server. This is implemented on the server-side in +/// server_Shutdown. It will TerminateProcess the RPC server, really really +/// making sure no one can use it anymore. +pub fn rpc_client_cleanup() { + unsafe { + _ = seh_wrapper_client_Shutdown(client_sudo_rpc_ClientIfHandle); + _ = RpcBindingFree(std::ptr::addr_of_mut!(client_sudo_rpc_ClientIfHandle)); + } +} + +#[allow(clippy::too_many_arguments)] +pub fn rpc_client_do_elevation_request( + parent_handle: HANDLE, + handles: &[HANDLE; 3], // in, out, err + sudo_mode: SudoMode, + application: Utf8Str, + args: Utf8Str, + target_dir: Utf8Str, + env_vars: Utf8Str, + event_id: GUID, + child: *mut HANDLE, +) -> HRESULT { + let mut pipe_handles = [HANDLE::default(); 3]; + let mut file_handles = [HANDLE::default(); 3]; + + for i in 0..3 { + match unsafe { GetFileType(handles[i]) } { + FILE_TYPE_PIPE => pipe_handles[i] = handles[i], + FILE_TYPE_DISK => file_handles[i] = handles[i], + _ => {} + } + } + + unsafe { + seh_wrapper_client_DoElevationRequest( + client_sudo_rpc_ClientIfHandle, + parent_handle, + &pipe_handles, + &file_handles, + sudo_mode.into(), + application, + args, + target_dir, + env_vars, + event_id, + child, + ) + } +} diff --git a/sudo/src/rpc_bindings_server.rs b/sudo/src/rpc_bindings_server.rs new file mode 100644 index 0000000..49bf3c4 --- /dev/null +++ b/sudo/src/rpc_bindings_server.rs @@ -0,0 +1,295 @@ +use crate::helpers::*; +use crate::{ + elevate_handler::handle_elevation_request, messages::ElevateRequest, rpc_bindings::Utf8Str, +}; +use std::ffi::{c_void, CStr}; +use std::mem::{size_of, take}; +use std::ptr::null_mut; +use std::sync::atomic::{AtomicBool, Ordering}; +use windows::Win32::Foundation::{ERROR_BUSY, GENERIC_ALL, HANDLE, PSID}; +use windows::{ + core::*, Win32::Security::Authorization::*, Win32::Security::*, Win32::System::Memory::*, + Win32::System::Rpc::*, Win32::System::SystemServices::*, Win32::System::Threading::*, +}; + +extern "C" { + static mut server_sudo_rpc_ServerIfHandle: *mut c_void; +} + +static mut EXPECTED_CLIENT_PID: u32 = 0; + +// Process-wide mutex to ensure that only one request is handled at a time. The +// bool inside the atomic is true if we've already started handling a request. +static RPC_SERVER_IN_USE: AtomicBool = AtomicBool::new(false); + +// * Context: The callback function may pass this handle to +// RpcImpersonateClient, RpcBindingServerFromClient, +// RpcGetAuthorizationContextForClient, or any other server side function that +// accepts a client binding handle to obtain information about the client. +// +// The callback function should return RPC_S_OK, if the client is allowed to +// call methods in this interface. +unsafe extern "system" fn rpc_server_callback( + _interface_uuid: *const c_void, + context: *const c_void, +) -> RPC_STATUS { + let mut client_handle = OwnedHandle::default(); + let status = I_RpcOpenClientProcess( + Some(context), + PROCESS_QUERY_LIMITED_INFORMATION.0, + &mut *client_handle as *mut _ as _, + ); + if status != RPC_S_OK { + return status; + } + + // Check #1: We'll check that the client process is the one we expected, + // when we were first started. + let client_pid = GetProcessId(*client_handle); // if this fails, it returns 0 + if client_pid == 0 || client_pid != EXPECTED_CLIENT_PID { + return RPC_S_ACCESS_DENIED; + } + + // Check #2: Check that the client process is the same as the server process. + if check_client(*client_handle).is_err() { + return RPC_S_ACCESS_DENIED; + } + + RPC_S_OK +} + +// MSDN regarding SetSecurityDescriptorSacl: +// > The SACL is referenced by, not copied into, the security descriptor. +// --> To return a SD we need to hold onto everything we allocated. Yay. +#[derive(Default)] +struct OwnedSecurityDescriptor { + pub sd: SECURITY_DESCRIPTOR, + sacl: OwnedLocalAlloc<*mut ACL>, + dacl: OwnedLocalAlloc<*mut ACL>, +} + +// Creates a descriptor of form +// D:(A;;GA;;;)S:(ML;;NWNRNX;;;ME) +fn create_security_descriptor_for_process(pid: u32) -> Result { + unsafe { + let mut s: OwnedSecurityDescriptor = Default::default(); + let psd = PSECURITY_DESCRIPTOR(&mut s.sd as *mut _ as _); + InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION)?; + + // SACL + { + let user = { + let process = + OwnedHandle::new(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid)?); + get_sid_for_process(*process)? + }; + + let ea = [EXPLICIT_ACCESS_W { + grfAccessPermissions: GENERIC_ALL.0, + grfAccessMode: SET_ACCESS, + grfInheritance: NO_INHERITANCE, + Trustee: TRUSTEE_W { + pMultipleTrustee: null_mut(), + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: PWSTR(&user.Sid as *const _ as _), + }, + }]; + + SetEntriesInAclW(Some(&ea), None, &mut *s.dacl).ok()?; + SetSecurityDescriptorDacl(psd, true, Some(*s.dacl), false)?; + } + + // DACL + { + // windows-rs doesn't have a definition for this macro. + const SECURITY_MAX_SID_SIZE: usize = 88; + + let mut sid_buffer = [0u8; SECURITY_MAX_SID_SIZE]; + let mut sid_len = sid_buffer.len() as u32; + let sid = PSID(&mut sid_buffer as *mut _ as _); + CreateWellKnownSid(WinMediumLabelSid, None, sid, &mut sid_len)?; + + const SACL_BUFFER_PREFIX_LEN: usize = + size_of::() + size_of::(); + let sacl_len = SACL_BUFFER_PREFIX_LEN as u32 + sid_len; + s.sacl.0 = LocalAlloc(LMEM_FIXED, sacl_len as usize)?.0 as _; + + InitializeAcl(*s.sacl, sacl_len, ACL_REVISION)?; + AddMandatoryAce( + *s.sacl, + ACL_REVISION, + ACE_FLAGS(0), + SYSTEM_MANDATORY_LABEL_NO_READ_UP + | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP + | SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP, + sid, + )?; + + SetSecurityDescriptorSacl(psd, true, Some(*s.sacl), false)?; + } + + Ok(s) + } +} + +pub fn rpc_server_setup(endpoint: &CStr, expected_client_pid: u32) -> Result<()> { + let owned_sd = create_security_descriptor_for_process(expected_client_pid)?; + + unsafe { + RpcServerUseProtseqEpA( + /* Protseq */ s!("ncalrpc"), + /* MaxCalls */ RPC_C_LISTEN_MAX_CALLS_DEFAULT, + /* Endpoint */ PCSTR(endpoint.as_ptr() as _), + /* SecurityDescriptor */ Some(&owned_sd.sd as *const _ as _), + ) + .ok()?; + RpcServerRegisterIf3( + /* IfSpec */ server_sudo_rpc_ServerIfHandle, + /* MgrTypeUuid */ None, + /* MgrEpv */ None, + /* Flags */ RPC_IF_ALLOW_LOCAL_ONLY | RPC_IF_ALLOW_SECURE_ONLY, + /* MaxCalls */ RPC_C_LISTEN_MAX_CALLS_DEFAULT, + /* MaxRpcSize */ u32::MAX, + /* IfCallback */ Some(rpc_server_callback), + /* SecurityDescriptor */ Some(&owned_sd.sd as *const _ as _), + ) + .ok()?; + + EXPECTED_CLIENT_PID = expected_client_pid; + + let res = RpcServerListen( + /* MinimumCallThreads */ 1, + /* MaxCalls */ RPC_C_LISTEN_MAX_CALLS_DEFAULT, + /* DontWait */ 0, + ); + if res.is_err() { + _ = RpcServerUnregisterIf(None, None, 0); + } + res.ok() + } +} + +// This is the RPC's sudo_rpc::Shutdown callback function. +#[no_mangle] +unsafe extern "C" fn server_Shutdown(_binding: *const c_void) { + _ = TerminateProcess(GetCurrentProcess(), 0); +} + +// This is the RPC's sudo_rpc::DoElevationRequest callback function. +#[no_mangle] +pub extern "C" fn server_DoElevationRequest( + _binding: *const c_void, + parent_handle: HANDLE, + pipe_handles: *const [HANDLE; 3], // in, out, err + file_handles: *const [HANDLE; 3], // in, out, err + sudo_mode: u32, + application: Utf8Str, + args: Utf8Str, + target_dir: Utf8Str, + env_vars: Utf8Str, + event_id: GUID, + child: *mut HANDLE, +) -> HRESULT { + // Only the first caller will get their request handled. Everyone else will + // be forced to bail out. + if RPC_SERVER_IN_USE.swap(true, Ordering::Relaxed) { + // We're already in the middle of handling a request. + return ERROR_BUSY.to_hresult(); + } + + // Here, we've locked the mutex and we're the only ones handling a request. + // + // And, we've set the atom to true, so if someone _does_ connect to us after + // this function releases the lock, then they'll also bail out. + + // Immediately unregister ourself. This will prevent a future caller from + // getting to us (but won't cancel the current request we're already in the + // middle of replying to). + unsafe { + _ = RpcMgmtStopServerListening(None); + _ = RpcServerUnregisterIf(None, None, 0); + } + + let result = wrap_elevate_request( + parent_handle, + pipe_handles, + file_handles, + sudo_mode, + application, + args, + target_dir, + env_vars, + event_id, + ) + .and_then(|req| handle_elevation_request(&req)); + + match result { + Ok(mut handle) => { + unsafe { child.write(take(&mut handle.0)) }; + HRESULT::default() + } + Err(err) => err.into(), + } +} + +#[allow(clippy::too_many_arguments)] +fn wrap_elevate_request( + parent_handle: HANDLE, + pipe_handles: *const [HANDLE; 3], // in, out, err + file_handles: *const [HANDLE; 3], // in, out, err + sudo_mode: u32, + application: Utf8Str, + args: Utf8Str, + target_dir: Utf8Str, + env_vars: Utf8Str, + event_id: GUID, +) -> Result { + let parent_pid = unsafe { GetProcessId(parent_handle) }; + let handles = unsafe { + let pipes = &*pipe_handles; + let files = &*file_handles; + std::array::from_fn(|i| if pipes[i].0 != 0 { pipes[i] } else { files[i] }) + }; + + Ok(ElevateRequest { + parent_pid, + handles, + sudo_mode: sudo_mode.try_into()?, + application: application.as_str()?.to_owned(), + args: unpack_string_list_from_rpc(args)?, + target_dir: target_dir.as_str()?.to_owned(), + env_vars: env_vars.as_str()?.to_owned(), + event_id, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_security_descriptor_for_process() { + fn sd_to_string(sd: PSECURITY_DESCRIPTOR) -> Result { + unsafe { + let mut buffer = PSTR::null(); + ConvertSecurityDescriptorToStringSecurityDescriptorA( + sd, + SDDL_REVISION, + DACL_SECURITY_INFORMATION + | LABEL_SECURITY_INFORMATION + | OWNER_SECURITY_INFORMATION, + &mut buffer, + None, + )?; + Ok(buffer.to_string()?) + } + } + + let s = create_security_descriptor_for_process(unsafe { GetCurrentProcessId() }).unwrap(); + let str = sd_to_string(PSECURITY_DESCRIPTOR(&s.sd as *const _ as _)).unwrap(); + assert!(str.starts_with("D:(A;;GA;;;")); + assert!(str.ends_with(")S:(ML;;NWNRNX;;;ME)")); + } +} diff --git a/sudo/src/run_handler.rs b/sudo/src/run_handler.rs new file mode 100644 index 0000000..b04409a --- /dev/null +++ b/sudo/src/run_handler.rs @@ -0,0 +1,549 @@ +use crate::elevate_handler::spawn_target_for_request; +use crate::helpers::*; +use crate::logging_bindings::event_log_request; +use crate::messages::ElevateRequest; +use crate::rpc_bindings::Utf8Str; +use crate::rpc_bindings_client::{ + rpc_client_cleanup, rpc_client_do_elevation_request, rpc_client_setup, +}; +use crate::{r, tracing}; +use std::env; +use std::ffi::{CString, OsStr}; +use std::path::Path; +use windows::Wdk::Foundation::{NtQueryObject, ObjectBasicInformation}; +use windows::Win32::System::WindowsProgramming::PUBLIC_OBJECT_BASIC_INFORMATION; +use windows::{ + core::*, Wdk::System::Threading::*, Win32::Foundation::*, Win32::Storage::FileSystem::*, + Win32::System::Console::*, Win32::System::Diagnostics::Debug::*, Win32::System::Rpc::*, + Win32::System::SystemInformation::*, Win32::System::Threading::*, Win32::UI::Shell::*, + Win32::UI::WindowsAndMessaging::*, +}; + +fn current_elevation_matches_request(is_admin: bool, _req: &ElevateRequest) -> bool { + // FUTURE TODO: actually support running as another user. + is_admin +} + +/// helper to find the process creation time for a given process handle +/// process_handle: handle to the process to get the creation time for. This is a non-owning handle. +fn get_process_creation_time(process_handle: HANDLE) -> Result { + unsafe { + // You actually have to pass in valid pointers to these, even if we don't need them. + let mut creation_time = FILETIME::default(); + let mut exit_time = FILETIME::default(); + let mut kernel_time = FILETIME::default(); + let mut user_time = FILETIME::default(); + GetProcessTimes( + process_handle, + &mut creation_time, + &mut exit_time, + &mut kernel_time, + &mut user_time, + )?; + Ok(creation_time) + } +} + +fn is_in_windows_dir(path: &Path) -> bool { + let path = HSTRING::from(path); + if path.len() >= MAX_PATH as usize { + return false; + } + let mut win_dir = [0u16; MAX_PATH as usize]; + let len = unsafe { GetWindowsDirectoryW(Some(&mut win_dir)) }; + if len == 0 || len >= MAX_PATH { + return false; + } + unsafe { PathIsPrefixW(PCWSTR(win_dir.as_ptr()), PCWSTR(path.as_ptr())).as_bool() } +} + +/// Attempts to modify this request to run the command in CMD, if the +/// "application" that was passed to us was really just a CMD intrinsic. This is +/// used to support things like `sudo dir. +/// +/// We don't do any modification if the parent process was some variety of +/// PowerShell. There's impossible to resolve issues repackaging the args back +/// into a PowerShell command, so we're hoping that the sudo.ps1 script will +/// handle that case instead. +/// +/// * Returns an error if we failed to get the parent pid, or otherwise lookup +/// info we needed. +/// * Returns true if the application was a CMD intrinsic AND we were spawned +/// from CMD, and we adjusted the args accordingly. +/// * Returns false if the application was not an intrinsic or cmdlet +fn adjust_args_for_intrinsics_and_cmdlets(req: &mut ElevateRequest) -> Result { + // First things first: Get our parent process PID, with NtQueryInformationProcess + let parent_pid = unsafe { + let mut process_info = PROCESS_BASIC_INFORMATION::default(); + let mut return_len = 0u32; + + let get_parent_pid = NtQueryInformationProcess( + GetCurrentProcess(), + ProcessBasicInformation, + &mut process_info as *mut _ as _, + std::mem::size_of::() as u32, + &mut return_len, + ) + .ok(); + if let Err(err) = get_parent_pid { + tracing::trace_log_message(&format!("Error getting parent pid: {:?}", err.code().0)); + return Err(err); + } + process_info.InheritedFromUniqueProcessId + }; + tracing::trace_log_message(&format!("parent_pid: {parent_pid:?}")); + // Now, open that process so we can query some more information about it. + // (stick it in an OwnedHandle so it gets closed when we're done with it) + let parent_process_handle = unsafe { + OwnedHandle::new(OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION, + false, + parent_pid.try_into().unwrap(), + )?) + }; + + // Sanity check time! + // Was the parent process started _before us_? + + // Compare the two. If the parent process was created _after_ us, then we want to bail (with Ok(false)) + unsafe { + let parent_process_creation_time = get_process_creation_time(*parent_process_handle)?; + let our_creation_time = get_process_creation_time(GetCurrentProcess())?; + if CompareFileTime(&parent_process_creation_time, &our_creation_time) == 1 { + // Parent process was created after us. Bail. + return Ok(false); + } + } + + // Now, get the full path to the parent process + let parent_process_path = + get_process_path_from_handle(*parent_process_handle).unwrap_or_default(); + + tracing::trace_log_message(&format!("parent_process_str: {:?}", parent_process_path)); + + if !parent_process_path.ends_with("cmd.exe") { + // It's not. Bail. + return Ok(false); + } + + // We're using the Windows dir here, because we might be a x64 sudo + // that's being run from a x86 cmd.exe (which is _actually_ in syswow64). + if !is_in_windows_dir(&parent_process_path) { + // It's not. Bail. + return Ok(false); + } + + // Here, our parent is in fact cmd.exe (any arch). + + if is_cmd_intrinsic(&req.application) { + tracing::trace_cmd_builtin_found(&req.application); + + req.args + .splice(0..0, ["/c".to_string(), req.application.clone()]); + + // Toss this back at _exactly our parent process_. This makes sure we + // don't try to invoke the x64 cmd.exe from a x86 cmd.exe + req.application = parent_process_path.to_string_lossy().to_string(); + return Ok(true); + } + + Ok(false) +} + +fn adjust_args_for_gui_exes(req: &mut ElevateRequest) { + // We did find the command. We're now gonna try to find out if the file + // is: + // - An command line exe + // - A GUI exe + // - Just a plain old file (not an exe) + // + // Depending on what it is, we'll need to modify our request to run it. + // A Windows GUI exe can just be shell executed directly. + // TODO: We may want to do other work in the future for plain, non-executable files. + + let (is_exe, is_gui) = match get_exe_subsystem(&req.application) { + Ok(subsystem) => (true, subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI), + Err(..) => (false, false), + }; + + tracing::trace_log_message(&format!("is_exe: {is_exe}")); + tracing::trace_log_message(&format!("is_gui: {is_gui}")); + + // TODO: figure out how to handle non-exe files. ShellExecute(runas, + // ...) doesn't do anything for them, and I'm not sure we can trivially + // have the service find out what the right verb is for an arbitrary + // extension. (this is the kind of comment I'm sure to be proven wrong + // about) + if is_gui { + tracing::trace_log_message("not cli exe. Force new window"); + req.sudo_mode = SudoMode::ForceNewWindow; + } +} + +pub fn run_target( + copy_env: bool, + args: &[&String], + sudo_mode: SudoMode, + requested_dir: Option, +) -> Result { + let manually_requested_dir = requested_dir.is_some(); + let req = prepare_request(copy_env, args, sudo_mode, requested_dir)?; + do_request(req, copy_env, manually_requested_dir) +} + +/// Constructs an ElevateRequest from the given arguments. We'll package up +/// handles, we'll separate out the application and args, and we'll do some +/// other work to make sure the request is ready to go. +/// +/// This also includes getting the absolute path to the requested application +/// (which involves hitting up the file system). If the target app is a GUI app, +/// we'll convert the request to run in a new window. +/// +/// If the app isn't actually an app, and it's instead a CMD intrinsic, we'll +/// convert the request to run in CMD (if we were _ourselves_ ran from CMD). +fn prepare_request( + copy_env: bool, + args: &[&String], + sudo_mode: SudoMode, + requested_dir: Option, +) -> Result { + let handle_indices = [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE]; + + // Get our stdin and stdout handles + let handles = handle_indices.map(|idx| unsafe { GetStdHandle(idx).ok().unwrap_or_default() }); + + // Is stdin or stdout a console? + let is_console = handles.map(|h| unsafe { + let mut mode = CONSOLE_MODE::default(); + GetConsoleMode(h, &mut mode).is_ok() + }); + + // Pass invalid handles if the handle is a console handle. If you don't, + // then RPC will explode trying to duplicate the console handle to the + // elevated process (because a console isn't a "pipe", but a file is) + let mut filtered_handles: [HANDLE; 3] = Default::default(); + for i in 0..3 { + if !is_console[i] { + filtered_handles[i] = handles[i]; + } + } + + // If they passed a directory, use that. Otherwise, "use the current dir" + // (with the known caveats about new window mode) + let actual_dir = match requested_dir { + Some(dir) => { + // If they passed a directory, we need to canonicalize it. This is + // because the elevated sudo will start in system32 (because we are + // ourselves, an exe that's in the Windows directory). This will + // make sure the elevated sudo gets the real path they asked for. + // + // DON'T use std::canonicalize though. That'll give us a UNC path + // and just about nothing actaully accepts those (CMD.EXE included) + absolute_path(Path::new(&dir))? + } + None => env::current_dir()?, + } + .to_string_lossy() + .into_owned(); + + // Build our request + let mut req = ElevateRequest { + parent_pid: std::process::id(), + handles: filtered_handles, + sudo_mode, + application: args[0].clone(), + args: args.iter().skip(1).map(|arg| arg.to_string()).collect(), + target_dir: actual_dir, + env_vars: copy_env.then(env_as_string).unwrap_or_default(), + event_id: GUID::new().unwrap(), + }; + + tracing::trace_run(&req, !is_console[0], !is_console[1]); + event_log_request(true, &req); + + // Does the application exist somewhere on the path? + let where_result = which::which(&req.application); + + if let Ok(path) = where_result { + // It's a real file that exists on the PATH. + + // Replace the request's application with the full path to the exe. This + // ensures that the elevated sudo will execute the same thing that was + // found here in the unelevated context. + + req.application = absolute_path(&path)?.to_string_lossy().to_string(); + adjust_args_for_gui_exes(&mut req); + } else { + tracing::trace_command_not_found(&req.application); + + // Maybe, it's a CMD intrinsic. If it is, we'll need to adjust the args + // to make a new CMD to run the command + // + // This will return + // * an error if we couldn't get at our parent PID (very unexpected) or + // open the parent handle + // * Ok(false): + // - if the parent was created after us, so we can't tell if it's CMD or not + // - if the parent wasn't CMD or it wasn't an intrinsic + // * Ok(true): The parent was CMD, it was an intrinsic, and the args + // were adjusted to account for this. + if !adjust_args_for_intrinsics_and_cmdlets(&mut req)? { + return Err(E_DIR_BAD_COMMAND_OR_FILE.into()); + } + } + Ok(req) +} + +fn do_request(req: ElevateRequest, copy_env: bool, manually_requested_dir: bool) -> Result { + // Are we already running as admin? If we are, we don't need to do a whole + // bunch of ShellExecute. We can just spawn the target exe.] + let is_admin = is_running_elevated()?; + + if current_elevation_matches_request(is_admin, &req) { + // println!("We're already running as admin. Just run the command."); + let mut child = spawn_target_for_request(&req)?; + match child.wait() { + Ok(status) => Ok(status.code().unwrap_or_default()), + Err(err) => Err(err.into()), + } + } else { + // We're not running elevated here. We need to start the + // elevated sudo and send it our request to handle. + + // In ForceNewWindow mode, we want to use ShellExecuteEx to create the + // target process, whenever possible. This has the benefit of having the + // UAC display the target app directly, and also avoiding any RPC calls + // at all. + // + // However, there are caveats which prevent us from using ShellExecuteEx + // in all cases: + // * We can't use ShellExecuteEx if we need to copy the environment, + // because ShellExecuteEx doesn't allow us to set the environment of + // the target process. So if they want environment variables copied, + // we need to use RPC. + // * ShellExecuteEx will always set the CWD to system32, if the target + // exe is in the Windows dir. It does this _deep_ in the OS and + // there's nothing we can do to avoid it. So, if the user has + // requested a CWD, we need to use RPC. + // - Theoretically, we only need to use RPC if the target app is in + // the Windows dir, but we'd need to recreate the internal logic of + // CreateProcess to resolve the commandline we've been given here + // to determine that. + let should_use_runas = + req.sudo_mode == SudoMode::ForceNewWindow && !copy_env && !manually_requested_dir; + + if should_use_runas { + tracing::trace_log_message("Direct ShellExecute"); + runas_admin(&req.application, &join_args(&req.args), SW_NORMAL)?; + Ok(0) + } else { + tracing::trace_log_message("starting RPC handoff"); + handoff_to_elevated(&req) + } + } +} + +fn handoff_to_elevated(req: &ElevateRequest) -> Result { + // Build a single string from the request's application and args + let parent_pid = std::process::id(); + + tracing::trace_log_message(&format!( + "running as user: '{}'", + get_current_user().as_ref().unwrap_or(h!("unknown")) + )); + + let path = env::current_exe().unwrap(); + let target_args = format!( + "elevate -p {parent_pid} {} {}", + req.application, + join_args(&req.args) + ); + tracing::trace_log_message(&format!("elevate request: '{target_args:?}'")); + runas_admin(&path, &target_args, SW_HIDE)?; + + // Subtle: Add our own CtrlC handler, so that we can ignore it. + // Otherwise, the console gets into a weird state, where we return + // control to the parent shell, but the child process is still + // running. Two clients in the same console is always weird. + unsafe { + _ = SetConsoleCtrlHandler(Some(ignore_ctrl_c), true); + } + + send_request_via_rpc(req) +} + +/// Connects to the elevated sudo instance via RPC, then makes a couple RPC +/// calls to send the request to the elevated sudo. +/// +/// In the case of success, this might not return until our target process +/// actually exits. +/// +/// This will return an error if we can't connect to the RPC server. However, +/// we'll return Ok regardless if the RPC call itself succeeded or not. The Ok() +/// value will be: +/// - 0 if the _target_ process exited successfully +/// - Anything else to indicate either an error in the RPC call, or the target +/// process exited with an error. +/// - Specifically be on the lookout for 1764 here, which is +/// RPC_S_CANNOT_SUPPORT +fn send_request_via_rpc(req: &ElevateRequest) -> Result { + let endpoint = generate_rpc_endpoint_name(unsafe { GetCurrentProcessId() }); + let endpoint = CString::new(endpoint).unwrap(); + + // Attempt to connect to our RPC server, with a backoff. This will try 10 + // times, backing off by 100ms each time (a total of 5 seconds) + + let mut tries = 0; + loop { + if tries > 10 { + return Err(ERROR_TIMEOUT.into()); + } + // Casting this name to a *const u8 is a little unsafe, but our + // endpoint names aren't gonna be running abreast of weird encoding + // edge cases, and RpcStringBindingCompose ultimately wants an + // unsigned char string. + let connect_result = rpc_client_setup(&endpoint); + + match connect_result { + RPC_STATUS(0) => break, + RPC_S_NOT_LISTENING => { + tries += 1; + std::thread::sleep(std::time::Duration::from_millis(100 * tries)) + } + _ => std::process::exit(connect_result.0), + } + } + + // The GetCurrentProcess() is not a "real" handle and unsuitable to be used with COM. + // -> We need to clone it first. + let h_real = unsafe { + let mut process = OwnedHandle::default(); + let current_process = GetCurrentProcess(); + DuplicateHandle( + current_process, + current_process, + current_process, + &mut *process, + 0, + true, + DUPLICATE_SAME_ACCESS, + )?; + process + }; + + tracing::trace_log_message(&format!("sending i/o/e handles: {:?}", req.handles)); + + let mut child_handle = OwnedHandle::default(); + let rpc_elevate = rpc_client_do_elevation_request( + *h_real, + &req.handles, + req.sudo_mode, + Utf8Str::new(&req.application), + Utf8Str::new(&pack_string_list_for_rpc(&req.args)), + Utf8Str::new(&req.target_dir), + Utf8Str::new(&req.env_vars), + req.event_id, + &mut *child_handle, + ); + + tracing::trace_log_message(&format!("RequestElevation result {rpc_elevate:?}")); + // Clean up (terminate) the RPC server we made. + rpc_client_cleanup(); + + rpc_elevate.ok()?; + + // Assert that handle_elevation_request() properly limited the handle access rights to just the bits that we needed. + if cfg!(debug_assertions) { + unsafe { + let mut info: PUBLIC_OBJECT_BASIC_INFORMATION = Default::default(); + NtQueryObject( + *child_handle, + ObjectBasicInformation, + Some(&mut info as *mut _ as _), + std::mem::size_of_val(&info) as _, + None, + ) + .ok()?; + + let expected = + PROCESS_DUP_HANDLE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE; + debug_assert!(info.GrantedAccess == expected.0); + } + } + + // If we were in new window mode, and we're here, then we're + // ShellExecuting sudo.exe, and then using the elevated sudo to create a + // new console window. In that case, we want to print an error message + // here - the elevated sudo will have exited as soon as the child is + // launched. + if req.sudo_mode == SudoMode::ForceNewWindow { + let translated_msg = r::IDS_LAUNCHEDNEWWINDOW.get(); + let replaced = translated_msg.replace("{0}", &req.application); + println!("{}", replaced); + Ok(0) + } else { + unsafe { + let mut status = 0u32; + _ = WaitForSingleObject(*child_handle, INFINITE); + GetExitCodeProcess(*child_handle, &mut status)?; + Ok(status as _) + } + } +} + +fn runas_admin(exe: &Exe, args: &Args, show: SHOW_WINDOW_CMD) -> Result<()> +where + Exe: AsRef + ?Sized, + Args: AsRef + ?Sized, +{ + runas_admin_impl(exe.as_ref(), args.as_ref(), show) +} + +fn runas_admin_impl(exe: &OsStr, args: &OsStr, show: SHOW_WINDOW_CMD) -> Result<()> { + let cwd = env::current_dir()?; + let h_exe = HSTRING::from(exe); + let h_commandline = HSTRING::from(args); + let h_cwd = HSTRING::from(cwd.as_os_str()); + let mut sei = SHELLEXECUTEINFOW { + cbSize: std::mem::size_of::() as u32, + fMask: SEE_MASK_NOCLOSEPROCESS, + lpVerb: w!("runas"), + lpFile: PCWSTR(h_exe.as_ptr()), + lpParameters: PCWSTR(h_commandline.as_ptr()), + lpDirectory: PCWSTR(h_cwd.as_ptr()), + nShow: show.0, + ..Default::default() + }; + unsafe { ShellExecuteExW(&mut sei) } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cmd_is_cui() { + let app_name = "cmd".to_string(); + let req = prepare_request(false, &[&app_name], SudoMode::Normal, None).unwrap(); + assert_eq!(req.sudo_mode, SudoMode::Normal); + } + #[test] + fn test_notepad_is_gui() { + let req = + prepare_request(false, &[&("notepad".to_string())], SudoMode::Normal, None).unwrap(); + // If we did in fact find notepad, then we should have set the mode to + // ForceNewWindow, since it's a GUI app. + assert_eq!(req.sudo_mode, SudoMode::ForceNewWindow); + + // I found in the past that `notepad.exe` worked, while `notepad` + // didn't. Just make sure they both do, for sanity's sake. + let req_exe = prepare_request( + false, + &[&("notepad.exe".to_string())], + SudoMode::Normal, + None, + ) + .unwrap(); + assert_eq!(req_exe.sudo_mode, SudoMode::ForceNewWindow); + } +} diff --git a/sudo/src/tests/mod.rs b/sudo/src/tests/mod.rs new file mode 100644 index 0000000..316c943 --- /dev/null +++ b/sudo/src/tests/mod.rs @@ -0,0 +1,26 @@ +#[cfg(test)] +mod tests { + use crate::helpers::*; + use windows::Win32::Foundation::*; + + #[test] + fn test_try_from_u32_for_sudo_mode() { + assert_eq!(SudoMode::try_from(0), Ok(SudoMode::Disabled)); + assert_eq!(SudoMode::try_from(1), Ok(SudoMode::ForceNewWindow)); + assert_eq!(SudoMode::try_from(2), Ok(SudoMode::DisableInput)); + assert_eq!(SudoMode::try_from(3), Ok(SudoMode::Normal)); + assert_eq!(SudoMode::try_from(4), Err(ERROR_INVALID_PARAMETER.into())); + } + #[test] + fn test_try_sudo_mode_to_u32() { + assert_eq!(u32::from(SudoMode::Disabled), 0); + assert_eq!(u32::from(SudoMode::ForceNewWindow), 1); + assert_eq!(u32::from(SudoMode::DisableInput), 2); + assert_eq!(u32::from(SudoMode::Normal), 3); + } + + #[test] + fn test_generate_rpc_endpoint_name() { + assert_eq!(generate_rpc_endpoint_name(1234), r"sudo_elevate_1234"); + } +} diff --git a/sudo/src/tracing.rs b/sudo/src/tracing.rs new file mode 100644 index 0000000..050d8ce --- /dev/null +++ b/sudo/src/tracing.rs @@ -0,0 +1,52 @@ +use sudo_events::SudoEvents; + +// tl:{6ffdd42d-46d9-5efe-68a1-3b18cb73a607} +static SUDO_EVENTS: std::sync::OnceLock = std::sync::OnceLock::new(); + +const PDT_PRODUCT_AND_SERVICE_PERFORMANCE: u64 = 0x0000000001000000; +// const PDT_PRODUCT_AND_SERVICE_USAGE: u64 = 0x0000000002000000; + +pub fn sudo_events() -> &'static SudoEvents { + SUDO_EVENTS.get_or_init(SudoEvents::new) +} + +use crate::messages::*; + +pub fn enable_tracing() { + sudo_events(); +} + +pub fn trace_log_message(message: &str) { + sudo_events().message(None, message); +} + +pub fn trace_command_not_found(exe_name: &str) { + sudo_events().command_not_found(None, exe_name); +} + +pub fn trace_cmd_builtin_found(exe_name: &str) { + sudo_events().cmd_builtin_found(None, exe_name); +} + +pub fn trace_run(req: &ElevateRequest, redirected_input: bool, redirected_output: bool) { + sudo_events().run( + None, + &req.application, + req.sudo_mode as u32, + req.parent_pid, + redirected_input, + redirected_output, + ); +} + +pub fn trace_modes(requested_mode: u32, allowed_mode: u32, policy_mode: u32) { + // We manually set the privacy tag to PDT_PRODUCT_AND_SERVICE_PERFORMANCE so + // that callers don't need to know that + sudo_events().modes( + None, + requested_mode, + allowed_mode, + policy_mode, + PDT_PRODUCT_AND_SERVICE_PERFORMANCE, + ); +} diff --git a/sudo/sudo.manifest b/sudo/sudo.manifest new file mode 100644 index 0000000..8a8a669 --- /dev/null +++ b/sudo/sudo.manifest @@ -0,0 +1,9 @@ + + + + + + true + + + diff --git a/sudo/sudo.rc b/sudo/sudo.rc new file mode 100644 index 0000000..4e8e780 --- /dev/null +++ b/sudo/sudo.rc @@ -0,0 +1,24 @@ +// Resource file (.rc) template for sudo. +// +// Our string resources are all auto-generated from our resw file. They'll be +// consumed from a generated .rc file, which will start with the content of this file. + +#include + +// Here, you'd usually want to +// +// #include "resource.h" +// +// To include your resource ID's. That doesn't work for us! +// We want to use the winres crate to auto-generate FILEVERSION et. al. If we +// want that to work, then we need to append the content of our generated file +// to the content winres generates (using append_rc_content). +// However, when we do it that way, the ultimate .rc file we end up using is one +// that's generated in our OUT_DIR, and that means it can't find the relative +// resource.h +// +// Instead, we'll use append_rc_content to _also_ include the header literally +// in the output .rc file. + +// Make sure to declare our exe icon here +IDI_APPICON ICON "..\\img\\ico\\sudo.ico" diff --git a/sudo_events/Cargo.toml b/sudo_events/Cargo.toml new file mode 100644 index 0000000..14e09bb --- /dev/null +++ b/sudo_events/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sudo_events" +version = "0.1.0" +edition = "2021" + +[dependencies] +win_etw_macros.workspace = true +win_etw_provider.workspace = true diff --git a/sudo_events/build.rs b/sudo_events/build.rs new file mode 100644 index 0000000..6a3a55e --- /dev/null +++ b/sudo_events/build.rs @@ -0,0 +1,31 @@ +use std::{io, path::Path}; + +// BODGY +// +// * As a part of the build process, we need to replace the fake GUID in our +// tracing lib with the real one. +// * This build script here will take the value out of the env var +// MAGIC_TRACING_GUID, and replace the fake GUID in the events_template.rs +// file with that one. +// * We'll write that file out to %OUT_DIR%/mangled_events.rs, and then include +// _that mangled file_ in our lib.rs file. +fn main() -> io::Result<()> { + let input = std::fs::read_to_string("src/events_template.rs")?; + // Is the MAGIC_TRACING_GUID env var set? If it is... + let output = match std::env::var("MAGIC_TRACING_GUID") { + Ok(guid) => { + println!("MAGIC_TRACING_GUID: {}", guid); + + // Replace the fake guid (ffffffff-ffff-ffff-ffff-ffffffffffff) with this one. + + input.replace("ffffffff-ffff-ffff-ffff-ffffffffffff", &guid) + } + Err(_) => input, + }; + let path = Path::new(&std::env::var("OUT_DIR").unwrap()).join("mangled_events.rs"); + println!( + "cargo:rerun-if-changed={}", + path.as_path().to_str().unwrap() + ); + std::fs::write(path.as_path(), output) +} diff --git a/sudo_events/src/events_template.rs b/sudo_events/src/events_template.rs new file mode 100644 index 0000000..c31ba39 --- /dev/null +++ b/sudo_events/src/events_template.rs @@ -0,0 +1,47 @@ + +use win_etw_macros::trace_logging_provider; +// Note: Generate GUID using TlgGuid.exe tool +#[trace_logging_provider( + name = "Microsoft.Windows.Sudo", + guid = "6ffdd42d-46d9-5efe-68a1-3b18cb73a607", + provider_group_guid = "ffffffff-ffff-ffff-ffff-ffffffffffff" +)] +// tl:{6ffdd42d-46d9-5efe-68a1-3b18cb73a607} + +pub trait SudoEvents { + fn command_not_found(exe_name: &str); + + fn cmd_builtin_found(exe_name: &str); + + fn message(message: &str); + + fn run( + exe_name: &str, + requested_mode: u32, + parent_pid: u32, + redirected_input: bool, + redirected_output: bool, + ); + + // TRACELOGGING EVENTS: + // + // These events need to add a PartA_PrivTags: u64 parameter to the end of + // the event. That should be filled with PDT_ProductAndServicePerformance or + // PDT_ProductAndServiceUsage. Our wrappers in tracing.rs should abstract + // that away. + // + // Additionally, we manually set the keyword to MICROSOFT_KEYWORD_MEASURES. + // However, we can't use that constant here, because the macro needs an + // actual _literal_. So, we use the value 0x0000400000000000 directly. + // MICROSOFT_KEYWORD_TELEMETRY is 0x0000200000000000, but that... doesn't work? + + // requested_mode: + // * 0: Use the allowed mode from the registry / policy + // * 1: Manually request forceNewWindow + // * 2: Manually request disableInput + // * 3: ??? We shouldn't get these. Requested mode is set by the CLI flags + // allowed_mode: Straightforward. The mode in the registry + // policy_mode: The mode set by the policy. If the policy isn't set, this should be 0xffffffff + #[event(keyword = 0x0000400000000000)] + fn modes(requested_mode: u32, allowed_mode: u32, policy_mode: u32, PartA_PrivTags: u64); +} diff --git a/sudo_events/src/lib.rs b/sudo_events/src/lib.rs new file mode 100644 index 0000000..b3c4d3a --- /dev/null +++ b/sudo_events/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/mangled_events.rs")); diff --git a/tools/gen-lang-codes.ps1 b/tools/gen-lang-codes.ps1 new file mode 100644 index 0000000..c541960 --- /dev/null +++ b/tools/gen-lang-codes.ps1 @@ -0,0 +1,153 @@ +# This is a list of what I think all the languages we need to support are. At +# the very least, it's all the languages that the Terminal's context menu are +# localized into. If we need more than that, go ahead and add more. This has to +# be the most complete list - the script that actually generates the .rc file +# will use whatever subest of languages are actually available. + +$languageCodes = @( +"af-ZA", +"am-ET", +"ar-SA", +"as-IN", +"az-Latn-AZ", +"bg-BG", +"bn-IN", +"bs-Latn-BA", +"ca-ES", +"ca-Es-VALENCIA", +"cs-CZ", +"cy-GB", +"da-DK", +"de-DE", +"el-GR", +"en-GB", +"en-US", +"es-ES", +"es-MX", +"et-EE", +"eu-ES", +"fa-IR", +"fi-FI", +"fil-PH", +"fr-CA", +"fr-FR", +"ga-IE", +"gd-gb", +"gl-ES", +"gu-IN", +"he-IL", +"hi-IN", +"hr-HR", +"hu-HU", +"hy-AM", +"id-ID", +"is-IS", +"it-IT", +"ja-JP", +"ka-GE", +"kk-KZ", +"km-KH", +"kn-IN", +"ko-KR", +"kok-IN", +"lb-LU", +"lo-LA", +"lt-LT", +"lv-LV", +"mi-NZ", +"mk-MK", +"ml-IN", +"mr-IN", +"ms-MY", +"mt-MT", +"nb-NO", +"ne-NP", +"nl-NL", +"nn-NO", +"or-IN", +"pa-IN", +"pl-PL", +"pt-BR", +"pt-PT", +"qps-ploc", +"qps-ploca", +"qps-plocm", +"quz-PE", +"ro-RO", +"ru-RU", +"sk-SK", +"sl-SI", +"sq-AL", +"sr-Cyrl-BA", +"sr-Cyrl-RS", +"sr-Latn-RS", +"sv-SE", +"ta-IN", +"te-IN", +"th-TH", +"tr-TR", +"tt-RU", +"ug-CN", +"uk-UA", +"ur-PK", +"uz-Latn-UZ", +"vi-VN", +"zh-CN", +"zh-TW" +) + +function Get-Language-Sublanguage-Constants { + param ( + [int]$lcid + ) + + $language = $lcid -band 0xFFFF + $sublanguage = ($lcid -shr 16) -band 0x3F + + # Language Constants + $languageConstant = "LANG_" + ([System.Globalization.CultureInfo]::GetCultureInfo($lcid).TwoLetterISOLanguageName).ToUpper() + + # Sublanguage Constants + $sublanguageConstant = "SUBLANG_" + ([System.Globalization.CultureInfo]::GetCultureInfo($lcid).Name.Split('-')[1]).ToUpper() + + return $language, $sublanguage, $languageConstant, $sublanguageConstant, ([System.Globalization.CultureInfo]::GetCultureInfo($lcid).ThreeLetterISOLanguageName).ToUpper() +} + +$languageHashTable = @{} + +foreach ($code in $languageCodes) { + $cultureInfo = New-Object System.Globalization.CultureInfo $code + $displayName = $cultureInfo.DisplayName + $neutralCulture = $cultureInfo.Parent.Name + $lcid = $cultureInfo.LCID + $language, $sublanguage, $languageConstant, $sublanguageConstant, $threeLetter = Get-Language-Sublanguage-Constants -lcid $lcid + + # trim out "LANG_" and "SUBLANG_ + $languageConstant = $languageConstant.Substring(5) + $sublanguageConstant = $sublanguageConstant.Substring(8) + + $hashTableValue = @( + # $cultureInfo.Name.ToUpper(), + # "", + $threeLetter, + $language, + $sublanguage, # $cultureInfo.Name.ToUpper() + '_' + $cultureInfo.Parent.Name.ToUpper(), + $displayName + ) + + $languageHashTable[$code] = $hashTableValue +} +# write list like: +# +# "eu-ES" = @("EUQ", "BASQUE", "DEFAULT", "Basque (Basque)"); +# $languageHashTable | % { + +foreach ($code in $languageCodes) { + $lang = $languageHashTable[$code] + # Get language and sublanguage constants + + write-host " `"$($code)`" = @(`"$($lang[0])`", `"$($lang[1])`", `"$($lang[2])`", `"$($lang[3])`");" + + # if ($_.Value -ne $null ) { + # write-host " `"$($_.Key)`" = @(`"$($_.Value[0])`", `"$($_.Value[1])`", `"$($_.Value[2])`", `"$($_.Value[3])`");" } +} diff --git a/tools/sudo.tvpp b/tools/sudo.tvpp new file mode 100644 index 0000000..903960a --- /dev/null +++ b/tools/sudo.tvpp @@ -0,0 +1,26 @@ + + + LocalLive + sudo for windows + + tl:{6ffdd42d-46d9-5efe-68a1-3b18cb73a607} + + + + 6ffdd42d-46d9-5efe-68a1-3b18cb73a607 + 0 + 0 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/tests.ipynb b/tools/tests.ipynb new file mode 100644 index 0000000..74cec52 --- /dev/null +++ b/tools/tests.ipynb @@ -0,0 +1,603 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sudo Test Scenarios\n", + "\n", + "Obviously, automated tests that require going through a UAC are challenging to write as unit tests, or run in CI. This notebook instead provides a way of listing a bunch of manual tests. This is all powered by the VsCode Polyglot Notebooks extension. Make sure you have that installed, so that you can run this notebook. \n", + "\n", + "(You'll need the [Polyglot Notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension for VsCode installed to run this notebook.)\n", + "\n", + "## Building\n", + "\n", + "First, start by building the code. We're going to stick the output exe into a `_sudo_` alias, to keep tests conscise. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "cargo build --target x86_64-pc-windows-msvc\n", + "new-alias -Force _sudo_ ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "new-alias -Force _sudo_ ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tests\n", + "\n", + "### Simple sanity tests\n", + "Just start py printing the error message:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dismiss this UAC. We should print an error message." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ cmd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo notepad` should elevate Notepad directly without triggering a UAC prompt for `notepad`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ notepad" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo --newWindow cmd` should elevate \"Command Prompt\" directly and spawn a new conhost." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ --newWindow cmd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo netstat -ab` should display network statistics and the process using them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ netstat -ab" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exiting `netstat` and `sudo` using Ctrl+C should terminate the processes. This test is going to spawn a new CMD in a new conhost window, ctrl+c in that window to verify it did work. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "conhost -- cmd.exe /k ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe netstat -ab" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Redirecting to pipes and files\n", + "\n", + "This command uses `dir /b` to list the contents of the directory, and `find /c /v \"\"` to count the lines (items) returned.\n", + "This should print a number greater than 0 if there are items in the directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ cmd /c dir /b \"C:\\Program Files\\WindowsApps\" `| find /c /v \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Env vars\n", + "\n", + "Running `sudo cmd`, without -E, doesn't propogate the environment variables. You should get `FooBar was %FooBar%`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "$env:FooBar = \"Hello World\"\n", + "_sudo_ cmd /c echo FooBar was '%FooBar%'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo -E ...` should pass the env vars to the child process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "$env:FooBar = \"Hello World\"\n", + "_sudo_ -E cmd /c echo FooBar was '%FooBar%'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo -E` with no command should not crash." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ -E" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo -N -E cmd` should elevate `sudo` directly, spawn a new conhost, and retain env vars. (The output will appear here in the notebook - close the conhost to continue.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "$env:FooBar = \"Hello new window\"\n", + "_sudo_ --new-window -E cmd /k echo FooBar was '%FooBar%'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe cmd` from an admin prompt should silently launch CMD without triggering UAC. (you'll need to paste this into the conhost that appears manually)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "Start-Process -verb runas cmd -ArgumentList \"/k cd /d $((Get-Location).Path)\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CMD intrinsics tests\n", + "\n", + "Running `sudo dir`, FROM CMD, should list the files in the current working directory. (Recall, we're in the `/tools` dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "cmd /c ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe dir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course, the notebook is running PowerShell. So, running `sudo dir` from PowerShell should not list files in the current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ dir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running the command `target\\x86_64-pc-windows-msvc\\debug\\sudo.exe fsutil volume allocationReport C: > out.txt`, then typing `out.txt` should display content in the text file.\n", + "\n", + "Ensure that the text file is not empty. This usually takes like 15-20 seconds to run, so patience. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "if (Test-Path -Path \"out.txt\") { Remove-Item -Path \"out.txt\" }\n", + "_sudo_ fsutil volume allocationReport C: > out.txt\n", + "(get-childItem -path out.txt).Length -gt 0\n", + "Remove-Item -Path \"out.txt\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setting Modes\n", + "\n", + "\n", + "Running `sudo --inline cmd` should exit with an error when `sudo` is set to disable input mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe config --enable disableInput\n", + "_sudo_ --inline cmd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Same as above, but with `sudo --disable-input cmd` should exit with an error when `sudo` is set to force new window mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe config --enable forceNewWindow\n", + "_sudo_ --disable-input cmd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, back to normal mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe config --enable normal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working directories\n", + "\n", + "Running `sudo -N cmd` in any path should start `cmd` in `C:\\windows\\system32`. (this will open a conhost in a new window)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ -N cmd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running `sudo -D . -N cmd` in any path should start in the original path. (the `/tools` directory)\n", + "\n", + "(again, output will actually appear in the notebook, close the conhost to continue)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ -D . -N cmd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This next one is a bit wacky. Search paths are... complicated. \n", + "\n", + "We're going to copy our built binary into system32 (under a different name, so it doesn't kill your existing sudo). \n", + "Then, we're going to copy `cmd` into a different path, one that's relative to our CWD. \n", + "\n", + "Then, we're going to run `sudo2 ..\\cmd.exe`. This should run the `cmd` from the relative path, not the one in system32. We can verify this by looking at the conhost that's spawned. It should display an error at the start like:\n", + "\n", + "```\n", + "The system cannot find message text for message number 0x2350 in the message file for Application.\n", + "\n", + "(c) Microsoft Corporation. All rights reserved.\n", + "Not enough memory resources are available to process this command.\n", + "```\n", + "\n", + "There will be two UACs to accept. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "_sudo_ cmd /c copy ..\\target\\x86_64-pc-windows-msvc\\debug\\sudo.exe C:\\Windows\\System32\\sudo2.exe /y\n", + "copy-item c:\\windows\\system32\\cmd.exe -destination .. -force\n", + "c:\\windows\\system32\\sudo2.exe -N ..\\cmd.exe\n", + "rm ..\\cmd.exe" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (PowerShell)", + "language": "PowerShell", + "name": ".net-pwsh" + }, + "language_info": { + "name": "polyglot-notebook" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "pwsh", + "items": [ + { + "aliases": [], + "languageName": "pwsh", + "name": "pwsh" + } + ] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/win32resources/Cargo.toml b/win32resources/Cargo.toml new file mode 100644 index 0000000..64396da --- /dev/null +++ b/win32resources/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "win32resources" +version = "0.1.0" +edition = "2021" diff --git a/win32resources/src/lib.rs b/win32resources/src/lib.rs new file mode 100644 index 0000000..22a64ea --- /dev/null +++ b/win32resources/src/lib.rs @@ -0,0 +1,79 @@ +//! Provides APIs for accessing Win32 resources, mainly string resources. +use std::borrow::Cow; +use std::ffi::c_void; +use std::ops::Deref; +use std::ptr::null_mut; +use std::slice::from_raw_parts; +use std::sync::OnceLock; + +#[allow(clippy::upper_case_acronyms)] +type HINSTANCE = *const c_void; + +extern "system" { + fn LoadStringW(hInstance: HINSTANCE, uID: u32, lpBuffer: *mut u16, cchBufferMax: i32) -> i32; +} + +extern "C" { + static __ImageBase: [u8; 0]; +} + +fn module_base() -> *const c_void { + unsafe { (&__ImageBase) as *const [u8; 0] as *const c_void } +} + +/* +#[macro_export] +macro_rules! def_image_base { + ( + $fn_name:ident + ) => { + fn $fn_name() -> &'static ModuleAddress { + extern "C" { + static _ImageBase: (); + } + (&_ImageBase) as *const () as *const core::ffi::c_void + } + } +} + */ + +pub struct StaticStringResource { + id: u32, + value: OnceLock>, + fallback: &'static str, +} + +impl StaticStringResource { + pub const fn new(id: u32, fallback: &'static str) -> Self { + Self { + id, + value: OnceLock::new(), + fallback, + } + } + + pub fn get(&self) -> &str { + self.value.get_or_init(|| { + let image_base = module_base(); + + // If this returns 0, then the string was not found. + let mut base: *const u16 = null_mut(); + let len = unsafe { LoadStringW(image_base, self.id, &mut base as *mut _ as *mut _, 0) }; + if len <= 0 { + return Cow::Borrowed(self.fallback); + } + + Cow::Owned(String::from_utf16_lossy(unsafe { + from_raw_parts(base, len as usize) + })) + }) + } +} + +impl Deref for StaticStringResource { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.get() + } +}