Harden DAP server

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Francesco Renzi
2026-03-12 17:15:34 +00:00
committed by GitHub
parent 8d6b38a428
commit a8f3b9195d
2 changed files with 53 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
@@ -18,6 +18,8 @@ namespace GitHub.Runner.Worker.Dap
public sealed class DapServer : RunnerService, IDapServer
{
private const string ContentLengthHeader = "Content-Length: ";
private const int MaxMessageSize = 10 * 1024 * 1024; // 10 MB
private const int MaxHeaderLineLength = 8192; // 8 KB
private TcpListener _listener;
private TcpClient _client;
@@ -27,6 +29,7 @@ namespace GitHub.Runner.Worker.Dap
private TaskCompletionSource<bool> _connectionTcs;
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
private int _nextSeq = 1;
private Task _connectionLoopTask;
private volatile bool _acceptConnections = true;
public override void Initialize(IHostContext hostContext)
@@ -41,7 +44,7 @@ namespace GitHub.Runner.Worker.Dap
Trace.Info("Debug session set");
}
public async Task StartAsync(int port, CancellationToken cancellationToken)
public Task StartAsync(int port, CancellationToken cancellationToken)
{
Trace.Info($"Starting DAP server on port {port}");
@@ -53,9 +56,9 @@ namespace GitHub.Runner.Worker.Dap
Trace.Info($"DAP server listening on 127.0.0.1:{port}");
// Start the connection loop in the background
_ = ConnectionLoopAsync(_cts.Token);
_connectionLoopTask = ConnectionLoopAsync(_cts.Token);
await Task.CompletedTask;
return Task.CompletedTask;
}
/// <summary>
@@ -142,10 +145,18 @@ namespace GitHub.Runner.Worker.Dap
/// </summary>
private void CleanupConnection()
{
try { _stream?.Close(); } catch { /* best effort */ }
try { _client?.Close(); } catch { /* best effort */ }
_stream = null;
_client = null;
_sendLock.Wait();
try
{
try { _stream?.Close(); } catch { /* best effort */ }
try { _client?.Close(); } catch { /* best effort */ }
_stream = null;
_client = null;
}
finally
{
_sendLock.Release();
}
}
public async Task WaitForConnectionAsync(CancellationToken cancellationToken)
@@ -172,7 +183,14 @@ namespace GitHub.Runner.Worker.Dap
try { _listener?.Stop(); }
catch { /* best effort */ }
await Task.CompletedTask;
if (_connectionLoopTask != null)
{
try
{
await Task.WhenAny(_connectionLoopTask, Task.Delay(5000));
}
catch { /* best effort */ }
}
Trace.Info("DAP server stopped");
}
@@ -309,6 +327,11 @@ namespace GitHub.Runner.Worker.Dap
throw new InvalidDataException("Missing Content-Length header");
}
if (contentLength > MaxMessageSize)
{
throw new InvalidDataException($"Message size {contentLength} exceeds maximum allowed size of {MaxMessageSize}");
}
var buffer = new byte[contentLength];
var totalRead = 0;
while (totalRead < contentLength)
@@ -356,6 +379,11 @@ namespace GitHub.Runner.Worker.Dap
previousWasCr = (c == '\r');
lineBuilder.Append(c);
if (lineBuilder.Length > MaxHeaderLineLength)
{
throw new InvalidDataException($"Header line exceeds maximum length of {MaxHeaderLineLength}");
}
}
}
@@ -383,16 +411,15 @@ namespace GitHub.Runner.Worker.Dap
public void SendMessage(ProtocolMessage message)
{
if (_stream == null)
{
return;
}
try
{
_sendLock.Wait();
try
{
if (_stream == null)
{
return;
}
message.Seq = _nextSeq++;
SendMessageInternal(message);
}
@@ -409,17 +436,16 @@ namespace GitHub.Runner.Worker.Dap
public void SendEvent(Event evt)
{
if (_stream == null)
{
Trace.Warning($"Cannot send event '{evt.EventType}': no client connected");
return;
}
try
{
_sendLock.Wait();
try
{
if (_stream == null)
{
Trace.Warning($"Cannot send event '{evt.EventType}': no client connected");
return;
}
evt.Seq = _nextSeq++;
SendMessageInternal(evt);
}
@@ -437,17 +463,16 @@ namespace GitHub.Runner.Worker.Dap
public void SendResponse(Response response)
{
if (_stream == null)
{
Trace.Warning($"Cannot send response for '{response.Command}': no client connected");
return;
}
try
{
_sendLock.Wait();
try
{
if (_stream == null)
{
Trace.Warning($"Cannot send response for '{response.Command}': no client connected");
return;
}
response.Seq = _nextSeq++;
SendMessageInternal(response);
}

View File

@@ -275,12 +275,14 @@ namespace GitHub.Runner.Worker
catch (OperationCanceledException) when (jobRequestCancellationToken.IsCancellationRequested)
{
Trace.Info("Job was cancelled before debugger client connected. Continuing without debugger.");
try { await dapServer.StopAsync(); } catch { }
dapServer = null;
debugSession = null;
}
catch (Exception ex)
{
Trace.Warning($"Failed to complete DAP handshake: {ex.Message}. Job will continue without debugging.");
try { await dapServer.StopAsync(); } catch { }
dapServer = null;
debugSession = null;
}