Skip to content

Commit 6088451

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. CVE: 2025-13751 Change-Id: Id6a13b0c8124117bcea2926b16607ef39344015a Signed-off-by: Lev Stipakov <[email protected]> Acked-by: Selva Nair <[email protected]>
1 parent 7791f53 commit 6088451

File tree

1 file changed

+51
-9
lines changed

1 file changed

+51
-9
lines changed

src/openvpnserv/interactive.c

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ ResetOverlapped(LPOVERLAPPED overlapped)
208208
typedef enum
209209
{
210210
peek,
211+
peek_timed,
211212
read,
212213
write
213214
} async_op_t;
@@ -260,7 +261,7 @@ AsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count,
260261
goto out;
261262
}
262263

263-
if (op == peek)
264+
if (op == peek || op == peek_timed)
264265
{
265266
PeekNamedPipe(pipe, NULL, 0, NULL, &bytes, NULL);
266267
}
@@ -281,6 +282,12 @@ PeekNamedPipeAsync(HANDLE pipe, DWORD count, LPHANDLE events)
281282
return AsyncPipeOp(peek, pipe, NULL, 0, count, events);
282283
}
283284

285+
static DWORD
286+
PeekNamedPipeAsyncTimed(HANDLE pipe, DWORD count, LPHANDLE events)
287+
{
288+
return AsyncPipeOp(peek_timed, pipe, NULL, 0, count, events);
289+
}
290+
284291
static DWORD
285292
ReadPipeAsync(HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
286293
{
@@ -437,11 +444,11 @@ GetStartupData(HANDLE pipe, STARTUP_DATA *sud)
437444
WCHAR *data = NULL;
438445
DWORD bytes, read;
439446

440-
bytes = PeekNamedPipeAsync(pipe, 1, &exit_event);
447+
bytes = PeekNamedPipeAsyncTimed(pipe, 1, &exit_event);
441448
if (bytes == 0)
442449
{
443-
MsgToEventLog(M_SYSERR, L"PeekNamedPipeAsync failed");
444-
ReturnLastError(pipe, L"PeekNamedPipeAsync");
450+
MsgToEventLog(M_ERR, L"Timeout waiting for startup data");
451+
ReturnError(pipe, ERROR_STARTUP_DATA, L"GetStartupData (timeout)", 1, &exit_event);
445452
goto err;
446453
}
447454

@@ -3248,6 +3255,7 @@ RunOpenvpn(LPVOID p)
32483255
size_t cmdline_size;
32493256
undo_lists_t undo_lists;
32503257
WCHAR errmsg[512] = L"";
3258+
BOOL flush_pipe = TRUE;
32513259

32523260
SECURITY_ATTRIBUTES inheritable = { .nLength = sizeof(inheritable),
32533261
.lpSecurityDescriptor = NULL,
@@ -3267,6 +3275,7 @@ RunOpenvpn(LPVOID p)
32673275

32683276
if (!GetStartupData(pipe, &sud))
32693277
{
3278+
flush_pipe = FALSE; /* client did not provide startup data */
32703279
goto out;
32713280
}
32723281

@@ -3562,7 +3571,10 @@ RunOpenvpn(LPVOID p)
35623571
Undo(&undo_lists);
35633572

35643573
out:
3565-
FlushFileBuffers(pipe);
3574+
if (flush_pipe)
3575+
{
3576+
FlushFileBuffers(pipe);
3577+
}
35663578
DisconnectNamedPipe(pipe);
35673579

35683580
free(ovpn_user);
@@ -3834,18 +3846,48 @@ ServiceStartInteractive(DWORD dwArgc, LPWSTR *lpszArgv)
38343846

38353847
while (TRUE)
38363848
{
3837-
if (ConnectNamedPipe(pipe, &overlapped) == FALSE && GetLastError() != ERROR_PIPE_CONNECTED
3838-
&& GetLastError() != ERROR_IO_PENDING)
3849+
if (!ConnectNamedPipe(pipe, &overlapped))
38393850
{
3840-
MsgToEventLog(M_SYSERR, L"Could not connect pipe");
3841-
break;
3851+
DWORD connect_error = GetLastError();
3852+
if (connect_error == ERROR_NO_DATA)
3853+
{
3854+
/*
3855+
* Client connected and disconnected before we could process it.
3856+
* Disconnect and retry instead of aborting the service.
3857+
*/
3858+
MsgToEventLog(M_ERR, L"ConnectNamedPipe returned ERROR_NO_DATA (client dropped)");
3859+
DisconnectNamedPipe(pipe);
3860+
ResetOverlapped(&overlapped);
3861+
continue;
3862+
}
3863+
else if (connect_error == ERROR_PIPE_CONNECTED)
3864+
{
3865+
/* No async I/O pending in this case; signal manually. */
3866+
SetEvent(overlapped.hEvent);
3867+
}
3868+
else if (connect_error != ERROR_IO_PENDING)
3869+
{
3870+
MsgToEventLog(M_SYSERR, L"Could not connect pipe");
3871+
break;
3872+
}
38423873
}
38433874

38443875
error = WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
38453876
if (error == WAIT_OBJECT_0)
38463877
{
38473878
/* Client connected, spawn a worker thread for it */
38483879
HANDLE next_pipe = CreateClientPipeInstance();
3880+
3881+
/* Avoid exceeding WaitForMultipleObjects MAXIMUM_WAIT_OBJECTS */
3882+
if (handle_count + 1 > MAXIMUM_WAIT_OBJECTS)
3883+
{
3884+
ReturnError(pipe, ERROR_CANT_WAIT, L"Too many concurrent clients", 1, &exit_event);
3885+
CloseHandleEx(&pipe);
3886+
pipe = next_pipe;
3887+
ResetOverlapped(&overlapped);
3888+
continue;
3889+
}
3890+
38493891
HANDLE thread = CreateThread(NULL, 0, RunOpenvpn, pipe, CREATE_SUSPENDED, NULL);
38503892
if (thread)
38513893
{

0 commit comments

Comments
 (0)