Skip to content

Commit 29d8ecc

Browse files
lstipakovcron2
authored andcommitted
interactive.c: harden pipe handling against misbehaving clients
- Handle ConnectNamedPipe ERROR_NO_DATA as a normal connect/drop race: log the drop, disconnect/reset that instance, and keep listening instead of letting a trivial local DoS stop the service. - Add a timed peek for startup data so a client that connects and sends nothing is timed out (IO_TIMEOUT) and rejected, instead of leaving a worker thread blocked forever and piling up handles. - Protect the accept loop from resource exhaustion: before spawning a worker, check the wait set and reject the client if adding another handle would exceed MAXIMUM_WAIT_OBJECTS; also skip FlushFileBuffers when no startup data was received to avoid hangs on silent clients. Without these fixes, a malicious local windows user can make the OpenVPN Interactive Service exit-on-error, thus breaking all OpenVPN connections until the service is restarted (or the system rebooted). Thus this has been classified as "local denial of service" and CVE-2025-13751 has been assigned. The patch in release/2.6 and release/2.5 is identical to the commit in 2.7_rc3, except for context diffs (formatting change) and L"" to TEXT("") adjustments. CVE: 2025-13751 Change-Id: Id6a13b0c8124117bcea2926b16607ef39344015a Signed-off-by: Lev Stipakov <[email protected]> Acked-by: Selva Nair <[email protected]>
1 parent f410584 commit 29d8ecc

File tree

1 file changed

+51
-10
lines changed

1 file changed

+51
-10
lines changed

src/openvpnserv/interactive.c

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ ResetOverlapped(LPOVERLAPPED overlapped)
215215

216216
typedef enum {
217217
peek,
218+
peek_timed,
218219
read,
219220
write
220221
} async_op_t;
@@ -268,7 +269,7 @@ AsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count,
268269
goto out;
269270
}
270271

271-
if (op == peek)
272+
if (op == peek || op == peek_timed)
272273
{
273274
PeekNamedPipe(pipe, NULL, 0, NULL, &bytes, NULL);
274275
}
@@ -289,6 +290,12 @@ PeekNamedPipeAsync(HANDLE pipe, DWORD count, LPHANDLE events)
289290
return AsyncPipeOp(peek, pipe, NULL, 0, count, events);
290291
}
291292

293+
static DWORD
294+
PeekNamedPipeAsyncTimed(HANDLE pipe, DWORD count, LPHANDLE events)
295+
{
296+
return AsyncPipeOp(peek_timed, pipe, NULL, 0, count, events);
297+
}
298+
292299
static DWORD
293300
ReadPipeAsync(HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
294301
{
@@ -454,11 +461,11 @@ GetStartupData(HANDLE pipe, STARTUP_DATA *sud)
454461
WCHAR *data = NULL;
455462
DWORD bytes, read;
456463

457-
bytes = PeekNamedPipeAsync(pipe, 1, &exit_event);
464+
bytes = PeekNamedPipeAsyncTimed(pipe, 1, &exit_event);
458465
if (bytes == 0)
459466
{
460-
MsgToEventLog(M_SYSERR, TEXT("PeekNamedPipeAsync failed"));
461-
ReturnLastError(pipe, L"PeekNamedPipeAsync");
467+
MsgToEventLog(M_ERR, TEXT("Timeout waiting for startup data"));
468+
ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData (timeout)", 1, &exit_event);
462469
goto err;
463470
}
464471

@@ -1794,6 +1801,7 @@ RunOpenvpn(LPVOID p)
17941801
size_t cmdline_size;
17951802
undo_lists_t undo_lists;
17961803
WCHAR errmsg[512] = L"";
1804+
BOOL flush_pipe = TRUE;
17971805

17981806
SECURITY_ATTRIBUTES inheritable = {
17991807
.nLength = sizeof(inheritable),
@@ -1817,6 +1825,7 @@ RunOpenvpn(LPVOID p)
18171825

18181826
if (!GetStartupData(pipe, &sud))
18191827
{
1828+
flush_pipe = FALSE; /* client did not provide startup data */
18201829
goto out;
18211830
}
18221831

@@ -2106,7 +2115,10 @@ RunOpenvpn(LPVOID p)
21062115
Undo(&undo_lists);
21072116

21082117
out:
2109-
FlushFileBuffers(pipe);
2118+
if (flush_pipe)
2119+
{
2120+
FlushFileBuffers(pipe);
2121+
}
21102122
DisconnectNamedPipe(pipe);
21112123

21122124
free(ovpn_user);
@@ -2338,19 +2350,48 @@ ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv)
23382350

23392351
while (TRUE)
23402352
{
2341-
if (ConnectNamedPipe(pipe, &overlapped) == FALSE
2342-
&& GetLastError() != ERROR_PIPE_CONNECTED
2343-
&& GetLastError() != ERROR_IO_PENDING)
2353+
if (!ConnectNamedPipe(pipe, &overlapped))
23442354
{
2345-
MsgToEventLog(M_SYSERR, TEXT("Could not connect pipe"));
2346-
break;
2355+
DWORD connect_error = GetLastError();
2356+
if (connect_error == ERROR_NO_DATA)
2357+
{
2358+
/*
2359+
* Client connected and disconnected before we could process it.
2360+
* Disconnect and retry instead of aborting the service.
2361+
*/
2362+
MsgToEventLog(M_ERR, TEXT("ConnectNamedPipe returned ERROR_NO_DATA (client dropped)"));
2363+
DisconnectNamedPipe(pipe);
2364+
ResetOverlapped(&overlapped);
2365+
continue;
2366+
}
2367+
else if (connect_error == ERROR_PIPE_CONNECTED)
2368+
{
2369+
/* No async I/O pending in this case; signal manually. */
2370+
SetEvent(overlapped.hEvent);
2371+
}
2372+
else if (connect_error != ERROR_IO_PENDING)
2373+
{
2374+
MsgToEventLog(M_SYSERR, TEXT("Could not connect pipe"));
2375+
break;
2376+
}
23472377
}
23482378

23492379
error = WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
23502380
if (error == WAIT_OBJECT_0)
23512381
{
23522382
/* Client connected, spawn a worker thread for it */
23532383
HANDLE next_pipe = CreateClientPipeInstance();
2384+
2385+
/* Avoid exceeding WaitForMultipleObjects MAXIMUM_WAIT_OBJECTS */
2386+
if (handle_count + 1 > MAXIMUM_WAIT_OBJECTS)
2387+
{
2388+
ReturnError(pipe, ERROR_CANT_WAIT, L"Too many concurrent clients", 1, &exit_event);
2389+
CloseHandleEx(&pipe);
2390+
pipe = next_pipe;
2391+
ResetOverlapped(&overlapped);
2392+
continue;
2393+
}
2394+
23542395
HANDLE thread = CreateThread(NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL);
23552396
if (thread)
23562397
{

0 commit comments

Comments
 (0)