From 8c496de1c8902c269710a8bab1a5147c71b197b4 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Thu, 4 Jun 2026 09:35:21 +0200 Subject: [PATCH] Allow skipping oom_score_adj write via ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ env var Unprivileged containerized runners (e.g. ARC on Kubernetes) cannot write to /proc//oom_score_adj and raise System.UnauthorizedAccessException. Setting ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ=true (or =1) suppresses the write entirely so operators are not flooded with access-denied exceptions when they know the container is already securely isolated. --- src/Runner.Sdk/ProcessInvoker.cs | 8 ++++++ src/Test/L0/ProcessInvokerL0.cs | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/Runner.Sdk/ProcessInvoker.cs b/src/Runner.Sdk/ProcessInvoker.cs index 7270faf5dc9..1b4dbd7dcae 100644 --- a/src/Runner.Sdk/ProcessInvoker.cs +++ b/src/Runner.Sdk/ProcessInvoker.cs @@ -871,6 +871,14 @@ private void NixKillProcessTree() #if OS_LINUX private void WriteProcessOomScoreAdj(int processId, int oomScoreAdj) { + var disableOomScoreAdj = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ"); + if (string.Equals(disableOomScoreAdj, "true", StringComparison.OrdinalIgnoreCase) || + string.Equals(disableOomScoreAdj, "1", StringComparison.OrdinalIgnoreCase)) + { + Trace.Verbose("Skipping oom_score_adj write: ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ is set (unprivileged container)."); + return; + } + try { string procFilePath = $"/proc/{processId}/oom_score_adj"; diff --git a/src/Test/L0/ProcessInvokerL0.cs b/src/Test/L0/ProcessInvokerL0.cs index dc3629e67aa..011a57ae31d 100644 --- a/src/Test/L0/ProcessInvokerL0.cs +++ b/src/Test/L0/ProcessInvokerL0.cs @@ -510,6 +510,53 @@ public async Task OomScoreAdjIsInherited() } } } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Common")] + public async Task OomScoreAdjIsSkipped_WhenDisableEnvVarSet() + { + // Unprivileged containers (e.g. ARC runners) cannot write to /proc//oom_score_adj. + // ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ suppresses the write entirely so no exception is raised. + string testProcPath = $"/proc/{Process.GetCurrentProcess().Id}/oom_score_adj"; + if (File.Exists(testProcPath)) + { + Environment.SetEnvironmentVariable("ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ", "true"); + try + { + using (TestHostContext hc = new(this)) + using (var tokenSource = new CancellationTokenSource()) + { + Tracing trace = hc.GetTrace(); + var processInvoker = new ProcessInvokerWrapper(); + processInvoker.Initialize(hc); + int oomScoreAdj = -9999; + processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => + { + oomScoreAdj = int.Parse(e.Data); + tokenSource.Cancel(); + }; + try + { + // The child process should inherit the parent's oom_score_adj unchanged (not 500). + int parentOomScore = int.Parse(File.ReadAllText(testProcPath).Trim()); + var proc = await processInvoker.ExecuteAsync("", "bash", "-c \"cat /proc/$$/oom_score_adj\"", null, false, null, false, null, false, false, + highPriorityProcess: false, + cancellationToken: tokenSource.Token); + Assert.Equal(parentOomScore, oomScoreAdj); + } + catch (OperationCanceledException) + { + trace.Info("Caught expected OperationCanceledException"); + } + } + } + finally + { + Environment.SetEnvironmentVariable("ACTIONS_RUNNER_DISABLE_OOM_SCORE_ADJ", null); + } + } + } #endif } }