From ff8b31e1454ede154e5940ff8be6df5c6da9a30a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 16:55:05 +0000 Subject: [PATCH 1/2] fix(dgw,agent): fix Windows service crash at startup Bump ceviche to 0.8.0, which passes a properly terminated service table to StartServiceCtrlDispatcherW. ceviche 0.7.0 passed a table without its null terminator entry, so Windows read leftover stack data as extra service entries. With the 2026.3.2 build, that made the Gateway service crash at startup on Windows Server 2016 (access violation in ntdll.dll). ceviche 0.8.0 also changes the service main function to return a u8 exit code, so the Gateway and Agent service entry points are updated accordingly. Issue: DGW-460 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013S6heAK1x7hF1rZDgpabwB --- Cargo.lock | 32 ++++++++++++++++---------------- devolutions-agent/Cargo.toml | 2 +- devolutions-agent/src/main.rs | 6 +++--- devolutions-gateway/Cargo.toml | 2 +- devolutions-gateway/src/main.rs | 6 +++--- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f8907cce..dbf967ea5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1037,9 +1037,9 @@ dependencies = [ [[package]] name = "ceviche" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5dea6d0064e3517b6f7ccf7d46713d9221547c74451d529f009b09cd2f9247" +checksum = "e1ce40845c8be69a6fbfbd8ae0d3a57112d39075f200407ed2c66050ec21984c" dependencies = [ "cfg-if", "chrono", @@ -1594,7 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.118", ] [[package]] @@ -2380,7 +2380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3212,12 +3212,12 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite 0.2.17", - "socket2 0.5.10", + "socket2 0.6.5", "system-configuration", "tokio 1.52.3", "tower-service", "tracing", - "windows-registry 0.5.3", + "windows-registry 0.6.1", ] [[package]] @@ -3751,7 +3751,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4941,7 +4941,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6088,7 +6088,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.3", "rustls 0.23.43", - "socket2 0.5.10", + "socket2 0.6.5", "thiserror 2.0.20", "tokio 1.52.3", "tracing", @@ -6128,9 +6128,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.5", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6647,7 +6647,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6747,7 +6747,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7302,7 +7302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7606,7 +7606,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9075,7 +9075,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/devolutions-agent/Cargo.toml b/devolutions-agent/Cargo.toml index d1b942405..5c4fe4ddb 100644 --- a/devolutions-agent/Cargo.toml +++ b/devolutions-agent/Cargo.toml @@ -19,7 +19,7 @@ async-trait = "0.1" base64 = "0.23" bytes = "1" camino = { version = "1.1", features = ["serde1"] } -ceviche = "0.7" +ceviche = "0.8" ctrlc = "3.5" devolutions-agent-shared = { path = "../crates/devolutions-agent-shared" } devolutions-gateway-task = { path = "../crates/devolutions-gateway-task" } diff --git a/devolutions-agent/src/main.rs b/devolutions-agent/src/main.rs index f8c9a048f..18c6f0f65 100644 --- a/devolutions-agent/src/main.rs +++ b/devolutions-agent/src/main.rs @@ -53,8 +53,8 @@ use devolutions_agent::enrollment::parse_enrollment_jwt; use self::service::{AgentService, DESCRIPTION, DISPLAY_NAME, SERVICE_NAME}; -const BAD_CONFIG_ERR_CODE: u32 = 1; -const START_FAILED_ERR_CODE: u32 = 2; +const BAD_CONFIG_ERR_CODE: u8 = 1; +const START_FAILED_ERR_CODE: u8 = 2; #[derive(Debug, PartialEq, Eq)] struct UpCommand { @@ -69,7 +69,7 @@ fn agent_service_main( _tx: mpsc::Sender, _args: Vec, _standalone_mode: bool, -) -> u32 { +) -> u8 { let Ok(conf_handle) = ConfHandle::init() else { // At this point, the logger is not yet initialized. return BAD_CONFIG_ERR_CODE; diff --git a/devolutions-gateway/Cargo.toml b/devolutions-gateway/Cargo.toml index 6d68ec6e8..92b310216 100644 --- a/devolutions-gateway/Cargo.toml +++ b/devolutions-gateway/Cargo.toml @@ -43,7 +43,7 @@ ironrdp-rdcleanpath = "0.2" ironrdp-tokio = "0.10" ironrdp-connector = "0.10" ironrdp-acceptor = "0.10" -ceviche = "0.7" +ceviche = "0.8" picky-krb = "0.12" kdc = "0.1" diff --git a/devolutions-gateway/src/main.rs b/devolutions-gateway/src/main.rs index 059f4044d..e5d57af2d 100644 --- a/devolutions-gateway/src/main.rs +++ b/devolutions-gateway/src/main.rs @@ -171,15 +171,15 @@ fn service_controller() -> Controller { enum GatewayServiceEvent {} -const BAD_CONFIG_ERR_CODE: u32 = 1; -const START_FAILED_ERR_CODE: u32 = 2; +const BAD_CONFIG_ERR_CODE: u8 = 1; +const START_FAILED_ERR_CODE: u8 = 2; fn gateway_service_main( rx: mpsc::Receiver>, _tx: mpsc::Sender>, _args: Vec, _standalone_mode: bool, -) -> u32 { +) -> u8 { let conf_handle = match ConfHandle::init() { Ok(conf_handle) => conf_handle, Err(error) => { From 8961e5ac669f8f3b9b821a08c55730536e55fd08 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 17:03:33 +0000 Subject: [PATCH 2/2] fix(dgw,agent): propagate the service exit code to the process ceviche 0.8 returns the service main exit code from `Controller::register`. Use it as the process exit status, so that a configuration or startup failure no longer exits with status 0 and service managers such as systemd (`Restart=on-failure`) see it. The Agent now also reports a registration failure and exits with status 1 instead of exiting silently with status 0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013S6heAK1x7hF1rZDgpabwB --- devolutions-agent/src/main.rs | 10 +++++++++- devolutions-gateway/src/main.rs | 15 ++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/devolutions-agent/src/main.rs b/devolutions-agent/src/main.rs index 18c6f0f65..aa1a0c5a8 100644 --- a/devolutions-agent/src/main.rs +++ b/devolutions-agent/src/main.rs @@ -318,7 +318,15 @@ fn main() { } } } else { - let _result = controller.register(service_main_wrapper); + // Propagate the service exit code so that service managers (e.g.: systemd) see failures. + match controller.register(service_main_wrapper) { + Ok(0) => {} + Ok(exit_code) => std::process::exit(i32::from(exit_code)), + Err(error) => { + eprintln!("[ERROR] Failed to register service: {error}"); + std::process::exit(1); + } + } } } diff --git a/devolutions-gateway/src/main.rs b/devolutions-gateway/src/main.rs index e5d57af2d..562395e53 100644 --- a/devolutions-gateway/src/main.rs +++ b/devolutions-gateway/src/main.rs @@ -63,6 +63,7 @@ extern crate tracing; mod service; +use std::process::ExitCode; use std::sync::mpsc; use anyhow::Context; @@ -76,8 +77,8 @@ use tap::prelude::*; use crate::service::{DESCRIPTION, DISPLAY_NAME, GatewayService, SERVICE_NAME}; -fn main() -> anyhow::Result<()> { - run().inspect_err(write_boot_stacktrace) +fn main() -> anyhow::Result { + run().map(ExitCode::from).inspect_err(write_boot_stacktrace) } fn write_boot_stacktrace(error: &anyhow::Error) { @@ -94,7 +95,8 @@ fn write_boot_stacktrace(error: &anyhow::Error) { } } -fn run() -> anyhow::Result<()> { +/// Returns the process exit code. +fn run() -> anyhow::Result { let (executable, cli_args) = parse_args_from_env()?; // Set the DGATEWAY_CONFIG_PATH if --config-path was provided. @@ -139,9 +141,12 @@ fn run() -> anyhow::Result<()> { devolutions_gateway::tls::install_default_crypto_provider(); if service_mode { - service_controller() + // Propagate the service exit code so that service managers (e.g.: systemd) see failures. + let exit_code = service_controller() .register(service_main_wrapper) .context("failed to register service")?; + + return Ok(exit_code); } else { let conf_handle = ConfHandle::init().context("unable to initialize configuration")?; let mut service = GatewayService::load(conf_handle).context("service loading failed")?; @@ -162,7 +167,7 @@ fn run() -> anyhow::Result<()> { } } - Ok(()) + Ok(0) } fn service_controller() -> Controller {