[Openvpn-devel,v1] win: don't use NULL DACL with system objects

Message ID 20260901202854.13746-1-gert@greenie.muc.de
State New
Headers
Series [Openvpn-devel,v1] win: don't use NULL DACL with system objects |

Commit Message

Gert Doering Sept. 1, 2026, 8:28 p.m. UTC
  From: Heiko Hund <heiko@ist.eigentlich.net>

The netsh semaphore used a NULL DACL, which allowed any user on a shared
system to block openvpn from running. Instances would time out and shut
down.

Similar with the --service exit event. Any user could signal it and shut
down the instance if the event name is known or can be retrieved from
the running process.

To prevent both, the objects are created with a DACL which allows access
to the creating user only. In case of the netsh semaphore this means that
only the first user running openvpn can run more instances. Other
accounts doing so will error out. The interactive service can be used to
prevent this from happening, since the netsh semaphore is only used when
the openvpn process runs privileged operations directly.

Github: OpenVPN/openvpn-private-issues#167
CVE: 2026-82312
Reported-By: DEBRAJ BASAK <https://in.linkedin.com/in/debrajbasak>
Change-Id: I787a7067f6ff040c6a1f43f384321bddf5efc97b
Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Lev Stipakov <lstipakov@gmail.com>
Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1889
---

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/+/1889
This mail reflects revision 1 of this Change.

Acked-by according to Gerrit (reflected above):
Lev Stipakov <lstipakov@gmail.com>
  

Patch

diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c
index 44b010d..dd88a22 100644
--- a/src/openvpn/win32.c
+++ b/src/openvpn/win32.c
@@ -35,6 +35,8 @@ 
 
 #include <minwindef.h>
 #include <winsock2.h>
+#include <accctrl.h>
+#include <aclapi.h>
 
 #include "buffer.h"
 #include "error.h"
@@ -146,6 +148,13 @@ 
     pause_exit_enabled = true;
 }
 
+/**
+ * @brief Initializes security attributes with a NULL DACL, allowing
+ *        unrestricted access to the resulting object.
+ *
+ * @param obj Security attributes structure to initialize.
+ * @return true on success, false otherwise.
+ */
 bool
 init_security_attributes_allow_all(struct security_attributes *obj)
 {
@@ -165,6 +174,93 @@ 
     return true;
 }
 
+/**
+ * @brief Initializes security attributes with a DACL restricted to the
+ *        current process user.
+ *
+ * The resulting DACL grants GENERIC_ALL access to the calling user only,
+ * so the created object cannot be opened, signaled or otherwise accessed
+ * by other users on the system. The allocated DACL must be released with
+ * free_security_attributes() once the security attributes are no longer
+ * needed.
+ *
+ * @param obj Security attributes structure to initialize.
+ * @return true on success, false otherwise.
+ */
+static bool
+init_security_attributes_allow_user(struct security_attributes *obj)
+{
+    bool ret = false;
+
+    CLEAR(*obj);
+    obj->sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+    obj->sa.lpSecurityDescriptor = &obj->sd;
+    obj->sa.bInheritHandle = FALSE;
+
+    if (!InitializeSecurityDescriptor(&obj->sd, SECURITY_DESCRIPTOR_REVISION))
+    {
+        return ret;
+    }
+
+    HANDLE token = NULL;
+    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
+    {
+        return ret;
+    }
+
+    PTOKEN_USER info = NULL;
+    DWORD info_len = 0;
+    if (!GetTokenInformation(token, TokenUser, info, info_len, &info_len)
+        && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+    {
+        goto out;
+    }
+
+    info = malloc(info_len);
+    if (!info || !GetTokenInformation(token, TokenUser, info, info_len, &info_len))
+    {
+        goto out;
+    }
+
+    EXPLICIT_ACCESS ea = { 0 };
+    ea.grfAccessPermissions = GENERIC_ALL;
+    ea.grfAccessMode = SET_ACCESS;
+    ea.grfInheritance = NO_INHERITANCE;
+    ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
+    ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
+    ea.Trustee.ptstrName = (LPTSTR)info->User.Sid;
+
+    if (SetEntriesInAcl(1, &ea, NULL, &obj->dacl) != ERROR_SUCCESS)
+    {
+        goto out;
+    }
+
+    if (SetSecurityDescriptorDacl(&obj->sd, TRUE, obj->dacl, FALSE))
+    {
+        ret = true;
+    }
+
+out:
+    free(info);
+    CloseHandle(token);
+    return ret;
+}
+
+/**
+ * @brief Releases resources allocated by init_security_attributes_allow_user().
+ *
+ * @param obj Security attributes structure to release.
+ */
+static void
+free_security_attributes(struct security_attributes *obj)
+{
+    if (obj->dacl)
+    {
+        LocalFree(obj->dacl);
+        obj->dacl = NULL;
+    }
+}
+
 void
 overlapped_io_init(struct overlapped_io *o, const struct frame *frame, BOOL event_state)
 {
@@ -504,7 +600,7 @@ 
         struct gc_arena gc = gc_new();
         const wchar_t *exit_event_nameW = wide_string(exit_event_name, &gc);
 
-        if (!init_security_attributes_allow_all(&sa))
+        if (!init_security_attributes_allow_user(&sa))
         {
             msg(M_ERR, "Error: win32_signal_open: init SA failed");
         }
@@ -526,6 +622,7 @@ 
                 ws->mode = WSO_MODE_SERVICE;
             }
         }
+        free_security_attributes(&sa);
         gc_free(&gc);
     }
     /* set the ctrl handler in both console and service modes */
@@ -751,14 +848,15 @@ 
     s->name = name;
     s->hand = NULL;
 
-    if (init_security_attributes_allow_all(&sa))
+    if (init_security_attributes_allow_user(&sa))
     {
         s->hand = CreateSemaphore(&sa.sa, 1, 1, name);
     }
+    free_security_attributes(&sa);
 
     if (s->hand == NULL)
     {
-        msg(M_WARN | M_ERRNO, "WARNING: Cannot create Win32 semaphore '%s'", name);
+        msg(M_ERR, "Cannot create Win32 semaphore '%s'", name);
     }
     else
     {
diff --git a/src/openvpn/win32.h b/src/openvpn/win32.h
index ef32062..8be3d96 100644
--- a/src/openvpn/win32.h
+++ b/src/openvpn/win32.h
@@ -63,6 +63,7 @@ 
 {
     SECURITY_ATTRIBUTES sa;
     SECURITY_DESCRIPTOR sd;
+    PACL dacl;
 };
 
 #define HANDLE_DEFINED(h) ((h) != NULL && (h) != INVALID_HANDLE_VALUE)
@@ -262,6 +263,11 @@ 
  *
  * It seems you can't run more than one instance
  * of netsh on the same machine at the same time.
+ *
+ * Its DACL is restricted to the creating user to prevent an unprivileged
+ * local user from starving it and DoS'ing running instances. This means
+ * different user accounts running OpenVPN directly, not via the interactive
+ * service, will make all but the first user's instances exit.
  */
 
 extern struct semaphore netcmd_semaphore;