[Openvpn-devel,v1] tapctl: prevent binary planting with netsh

Message ID 20260902095734.26857-1-gert@greenie.muc.de
State New
Headers
Series [Openvpn-devel,v1] tapctl: prevent binary planting with netsh |

Commit Message

Gert Doering Sept. 2, 2026, 9:57 a.m. UTC
  From: Heiko Hund <heiko@ist.eigentlich.net>

Provide a application name to CreateProcess(), so that it doesn't try to
locate an executable - somewhere. Instead construct the full path to
netsh.exe in the system dir and pass that to the function instead of NULL.

Discovered and reported by BreachX Zero Day Labs, using Typhon AI Mil v2.
Contributing Researcher: Vivek Parikh.

Reported-by: Vivek Parikh <vivek.parikh@breachx.ai>
Tested-by: Vivek Parikh <vivek.parikh@breachx.ai>
Github: OpenVPN/openvpn-private-issues#164
CVE: 2026-84226
Change-Id: I70282985a7e8e46add92b2277674cbca6bce39c1
Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Razvan Cojocaru <razvanc@mailbox.org>
Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1890
---

This change was reviewed on Gerrit and approved by at least one
developer. I request to merge it to master.

Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1890
This mail reflects revision 1 of this Change.

Acked-by according to Gerrit (reflected above):
Razvan Cojocaru <razvanc@mailbox.org>
  

Comments

Gert Doering Sept. 2, 2026, 11:05 a.m. UTC | #1
Change looks reasonable, detailed review from Razvan both in "private GH"
and +2 on Gerrit (thanks).  Compile- and unit-tested via GHA.

Your patch has been applied to the master, release/2.7 and release/2.6
branch.  2.6 needed a bit of massaging due to different wrapping in
the old CreateProcessW() call - but uncrustify is also fine with the
new wrapping, so the new code is 1:1 the same in 2.6 and onwards.

commit 03f8b88898b06d0d0acb3bded18eba82fa2e6277 (master)
commit 19b1b9f82d93b481b4a9a6c2a22782ee7a630186 (release/2.7)
commit d836a52b7f8117558d36b6f7133f2812b838a475 (release/2.6)
Author: Heiko Hund
Date:   Wed Sep 2 11:57:29 2026 +0200

     tapctl: prevent binary planting with netsh

     Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
     Acked-by: Razvan Cojocaru <razvanc@mailbox.org>
     Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1890
     Message-Id: <20260902095734.26857-1-gert@greenie.muc.de>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg38894.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering
  

Patch

diff --git a/src/tapctl/tap.c b/src/tapctl/tap.c
index 1d94988..769df6c 100644
--- a/src/tapctl/tap.c
+++ b/src/tapctl/tap.c
@@ -921,32 +921,38 @@ 
                                     bEnable ? enable_device : disable_device, pbRebootRequired);
 }
 
-/* stripped version of ExecCommand in interactive.c */
 static DWORD
-ExecCommand(const WCHAR *cmdline)
+ExecNetsh(PWSTR cmdline)
 {
     DWORD exit_code;
     STARTUPINFOW si;
     PROCESS_INFORMATION pi;
     DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
-    WCHAR *cmdline_dup = NULL;
 
     ZeroMemory(&si, sizeof(si));
     ZeroMemory(&pi, sizeof(pi));
-
     si.cb = sizeof(si);
 
-    /* CreateProcess needs a modifiable cmdline: make a copy */
-    cmdline_dup = _wcsdup(cmdline);
-    if (cmdline_dup
-        && CreateProcessW(NULL, cmdline_dup, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))
+    WCHAR appName[MAX_PATH];
+    WCHAR netsh_exe[] = L"\\netsh.exe";
+    UINT sysdir_len = GetSystemDirectoryW(appName, _countof(appName));
+    if (sysdir_len == 0)
+    {
+        wcscpy_s(appName, _countof(appName), L"C:\\Windows\\system32");
+    }
+    else if (sysdir_len + _countof(netsh_exe) > _countof(appName))
+    {
+        return ERROR_INSUFFICIENT_BUFFER;
+    }
+    wcscat_s(appName, _countof(appName), netsh_exe);
+
+    if (CreateProcessW(appName, cmdline, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))
     {
         WaitForSingleObject(pi.hProcess, INFINITE);
         if (!GetExitCodeProcess(pi.hProcess, &exit_code))
         {
             exit_code = GetLastError();
         }
-
         CloseHandle(pi.hProcess);
         CloseHandle(pi.hThread);
     }
@@ -955,7 +961,6 @@ 
         exit_code = GetLastError();
     }
 
-    free(cmdline_dup);
     return exit_code;
 }
 
@@ -1013,7 +1018,7 @@ 
 
     free(szOldName);
 
-    dwResult = ExecCommand(szCmdLine);
+    dwResult = ExecNetsh(szCmdLine);
     free(szCmdLine);
 
     if (dwResult != ERROR_SUCCESS)