[Openvpn-devel,v3] Require at least 100MB of mlock()-able memory if --mlock is used.

Message ID 20210310124808.14741-1-gert@greenie.muc.de
State Accepted
Headers show
Series [Openvpn-devel,v3] Require at least 100MB of mlock()-able memory if --mlock is used. | expand

Commit Message

Gert Doering March 10, 2021, 1:48 a.m. UTC
If --mlock is used, the amount of memory OpenVPN can use is guarded
by the RLIMIT_MEMLOCK value (see mlockall(2)).  The OS default for this
is usually 64 Kbyte, which is enough for OpenVPN to initialize, but
as soon as the first TLS handshake comes it, OpenVPN will crash due
to "ouf of memory", and might even end up in a crash loop.

Steady-state OpenVPN requires between 8 MB and 30-50 MB (servers with
many concurrent clients) of memory.  TLS renegotiation with EC keys
requires up to 90 MB of transient memory.

So: with this patch, we check if getrlimit() is available, and if yes,
log the amount of mlock'able memory.  If the amount is below 100 MB,
which is an arbitrary value "large enough for most smaller deployments",
we try to increase the limits to 100 MB, and abort if this fails.

v2:
  change arbitrary number to 100 MB, introduce #define for it
  not only check but also increase with setrlimit()
  uncrustify fixes

v3:
  OpenSolaris has mlockall() and getrlimit(), but no RLIMIT_MEMLOCK -
    make code conditional on HAVE_GETRLIMIT *and* RLIMIT_MEMLOCK
  add Changes.rst entry

Trac: #1390

Signed-off-by: Gert Doering <gert@greenie.muc.de>
---
 Changes.rst                          |  5 +++++
 configure.ac                         |  2 +-
 doc/man-sections/generic-options.rst |  7 +++++++
 src/openvpn/platform.c               | 29 ++++++++++++++++++++++++++++
 src/openvpn/platform.h               |  4 ++++
 5 files changed, 46 insertions(+), 1 deletion(-)

Comments

Gert Doering March 11, 2021, 6:23 a.m. UTC | #1
Thanks, Selva.

v3 has actually been buildbot-tested on all the platforms (I tested
v2 after the ACK, and it failed OpenSolaris, so I grumbled and adjusted
the #ifdef's...).  It has been "tested for real" on Linux, which has
"prlimit" to tell you the currently-active limits for *other* processes 
- haven't found something equivalent on the BSDs, so only "it will
report the right value(s) and not explode"-tested (+ buildbot compile).

Patch has been applied to the master branch.

commit b0bff5590152fbcc5b4d47c18817838fd00c58c3
Author: Gert Doering
Date:   Wed Mar 10 13:48:08 2021 +0100

     Require at least 100MB of mlock()-able memory if --mlock is used.

     Signed-off-by: Gert Doering <gert@greenie.muc.de>
     Acked-by: Selva Nair <selva.nair@gmail.com>
     Message-Id: <20210310124808.14741-1-gert@greenie.muc.de>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg21657.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering
Selva Nair March 11, 2021, 7:12 a.m. UTC | #2
Hi,

On Thu, Mar 11, 2021 at 12:24 PM Gert Doering <gert@greenie.muc.de> wrote:

> Thanks, Selva.
>
> v3 has actually been buildbot-tested on all the platforms (I tested
> v2 after the ACK, and it failed OpenSolaris, so I grumbled and adjusted
> the #ifdef's...).  It has been "tested for real" on Linux, which has
> "prlimit" to tell you the currently-active limits for *other* processes
> - haven't found something equivalent on the BSDs, so only "it will
> report the right value(s) and not explode"-tested (+ buildbot compile).
>

prlimit on Linux is handy as it can change the limits of a running process
as well.
On FreeBSD probably "limits -P pid ... " can do the same?

Selva

>
> Patch has been applied to the master branch.
>
> commit b0bff5590152fbcc5b4d47c18817838fd00c58c3
> Author: Gert Doering
> Date:   Wed Mar 10 13:48:08 2021 +0100
>
>      Require at least 100MB of mlock()-able memory if --mlock is used.
>
>      Signed-off-by: Gert Doering <gert@greenie.muc.de>
>      Acked-by: Selva Nair <selva.nair@gmail.com>
>      Message-Id: <20210310124808.14741-1-gert@greenie.muc.de>
>      URL:
> https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg21657.html
>      Signed-off-by: Gert Doering <gert@greenie.muc.de>
>
>
> --
> kind regards,
>
> Gert Doering
>
>
>
> _______________________________________________
> Openvpn-devel mailing list
> Openvpn-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/openvpn-devel
>
<div dir="ltr"><div>Hi,</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Mar 11, 2021 at 12:24 PM Gert Doering &lt;<a href="mailto:gert@greenie.muc.de">gert@greenie.muc.de</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Thanks, Selva.<br>
<br>
v3 has actually been buildbot-tested on all the platforms (I tested<br>
v2 after the ACK, and it failed OpenSolaris, so I grumbled and adjusted<br>
the #ifdef&#39;s...).  It has been &quot;tested for real&quot; on Linux, which has<br>
&quot;prlimit&quot; to tell you the currently-active limits for *other* processes <br>
- haven&#39;t found something equivalent on the BSDs, so only &quot;it will<br>
report the right value(s) and not explode&quot;-tested (+ buildbot compile).<br></blockquote><div><br></div><div>prlimit on Linux is handy as it can change the limits of a running process as well.</div><div>On FreeBSD probably &quot;limits -P pid ... &quot; can do the same?</div><div><br></div><div>Selva </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
Patch has been applied to the master branch.<br>
<br>
commit b0bff5590152fbcc5b4d47c18817838fd00c58c3<br>
Author: Gert Doering<br>
Date:   Wed Mar 10 13:48:08 2021 +0100<br>
<br>
     Require at least 100MB of mlock()-able memory if --mlock is used.<br>
<br>
     Signed-off-by: Gert Doering &lt;<a href="mailto:gert@greenie.muc.de" target="_blank">gert@greenie.muc.de</a>&gt;<br>
     Acked-by: Selva Nair &lt;<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>&gt;<br>
     Message-Id: &lt;<a href="mailto:20210310124808.14741-1-gert@greenie.muc.de" target="_blank">20210310124808.14741-1-gert@greenie.muc.de</a>&gt;<br>
     URL: <a href="https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg21657.html" rel="noreferrer" target="_blank">https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg21657.html</a><br>
     Signed-off-by: Gert Doering &lt;<a href="mailto:gert@greenie.muc.de" target="_blank">gert@greenie.muc.de</a>&gt;<br>
<br>
<br>
--<br>
kind regards,<br>
<br>
Gert Doering<br>
<br>
<br>
<br>
_______________________________________________<br>
Openvpn-devel mailing list<br>
<a href="mailto:Openvpn-devel@lists.sourceforge.net" target="_blank">Openvpn-devel@lists.sourceforge.net</a><br>
<a href="https://lists.sourceforge.net/lists/listinfo/openvpn-devel" rel="noreferrer" target="_blank">https://lists.sourceforge.net/lists/listinfo/openvpn-devel</a><br>
</blockquote></div></div>
Gert Doering March 11, 2021, 7:42 a.m. UTC | #3
HI,

On Thu, Mar 11, 2021 at 01:12:04PM -0500, Selva Nair wrote:
> prlimit on Linux is handy as it can change the limits of a running process
> as well.

Yes :-) - my week is full of exciting discoveries.

> On FreeBSD probably "limits -P pid ... " can do the same?

Indeed!  Thanks, didn't follow the right references, it seems...

gert@chekov.greenie.muc.de:/tmp$ limits -P 13222
Resource limits (current):
[..]
  memorylocked           102400 kB

... so, the claim of OpenVPN pid 13222 "I have set this to 100M" is true :-)

gert

Patch

diff --git a/Changes.rst b/Changes.rst
index 62008e8d..d6be1050 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -15,6 +15,11 @@  Compatibility with OpenSSL in FIPS mode
     requirements/recommendation of FIPS 140-2. This just allows OpenVPN
     to be run on a system that be configured OpenSSL in FIPS mode.
 
+``mlock`` will now check if enough memlock-able memory has been reserved,
+    and if less than 100MB RAM are available, use setrlimit() to upgrade
+    the limit.  See Trac #1390.  Not available on OpenSolaris.
+
+
 Deprecated features
 -------------------
 ``inetd`` has been removed
diff --git a/configure.ac b/configure.ac
index 1ab8fe59..c65df3e2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -645,7 +645,7 @@  AC_FUNC_FORK
 
 AC_CHECK_FUNCS([ \
 	daemon chroot getpwnam setuid nice system getpid dup dup2 \
-	getpass syslog openlog mlockall getgrnam setgid \
+	getpass syslog openlog mlockall getrlimit getgrnam setgid \
 	setgroups stat flock readv writev time gettimeofday \
 	ctime memset vsnprintf strdup \
 	setsid chdir putenv getpeername unlink \
diff --git a/doc/man-sections/generic-options.rst b/doc/man-sections/generic-options.rst
index d5f08839..c026529e 100644
--- a/doc/man-sections/generic-options.rst
+++ b/doc/man-sections/generic-options.rst
@@ -237,6 +237,13 @@  which mode OpenVPN is configured as.
   likely fail. The limit can be increased using ulimit or systemd
   directives depending on how OpenVPN is started.
 
+  If the platform has the getrlimit(2) system call, OpenVPN will check
+  for the amount of mlock-able memory before calling mlockall(2), and
+  tries to increase the limit to 100 MB if less than this is configured.
+  100 Mb is somewhat arbitrary - it is enough for a moderately-sized
+  OpenVPN deployment, but the memory usage might go beyond that if the
+  number of concurrent clients is high.
+
 --nice n
   Change process priority after initialization (``n`` greater than 0 is
   lower priority, ``n`` less than zero is higher priority).
diff --git a/src/openvpn/platform.c b/src/openvpn/platform.c
index ef688c23..3bf95f84 100644
--- a/src/openvpn/platform.c
+++ b/src/openvpn/platform.c
@@ -193,6 +193,35 @@  void
 platform_mlockall(bool print_msg)
 {
 #ifdef HAVE_MLOCKALL
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK)
+#define MIN_LOCKED_MEM_MB 100
+    struct rlimit rl;
+    if (getrlimit(RLIMIT_MEMLOCK, &rl) < 0)
+    {
+        msg(M_WARN | M_ERRNO, "WARNING: getrlimit(RLIMIT_MEMLOCK) failed");
+    }
+    else
+    {
+        msg(M_INFO, "mlock: MEMLOCK limit: soft=%ld KB, hard=%ld KB",
+            ((long int) rl.rlim_cur) / 1024, ((long int) rl.rlim_max) / 1024);
+        if (rl.rlim_cur < MIN_LOCKED_MEM_MB*1024*1024)
+        {
+            msg(M_INFO, "mlock: RLIMIT_MEMLOCK < %d MB, increase limit",
+                MIN_LOCKED_MEM_MB);
+            rl.rlim_cur = MIN_LOCKED_MEM_MB*1024*1024;
+            if (rl.rlim_max < rl.rlim_cur)
+            {
+                rl.rlim_max = rl.rlim_cur;
+            }
+            if (setrlimit(RLIMIT_MEMLOCK, &rl) < 0)
+            {
+                msg(M_FATAL | M_ERRNO, "ERROR: setrlimit() failed");
+            }
+        }
+    }
+#endif
+
     if (mlockall(MCL_CURRENT | MCL_FUTURE))
     {
         msg(M_WARN | M_ERRNO, "WARNING: mlockall call failed");
diff --git a/src/openvpn/platform.h b/src/openvpn/platform.h
index 01f3200c..02c23e38 100644
--- a/src/openvpn/platform.h
+++ b/src/openvpn/platform.h
@@ -48,6 +48,10 @@ 
 #include <stdio.h>
 #endif
 
+#ifdef HAVE_GETRLIMIT
+#include <sys/resource.h>
+#endif
+
 #include "basic.h"
 #include "buffer.h"