On Sun, Jun 29, 2025 at 12:03:59AM -0400, Daniel Kahn Gillmor wrote:
> By recording a copy of LISTEN_FDNAMES, we make it possible to learn
> mappings from file descriptor labels (e.g., as set by
> FileDescriptorName= in systemd.socket(5)).
> 

Just now noticing that this hasn't been reviewed yet...

In general, it's okay to ping the list after a week or so if you feel
like your patch has been overlooked.

> This also makes it possible to invoke check_socket_activation() more
> than once and have it return the same value each time.
> 
> This is one step toward addressing
> https://gitlab.com/qemu-project/qemu/-/issues/3011
> 
> Since we can't count on the buffer returned from getenv
> persisting (getenv is documented as non-re-entrant), we need to keep a
> copy of it around in case multiple subsystems want to interrogate it.

getenv()'s result is non-reliable IF the application is calling
setenv()/putenv() to be changing the environment.  But if you treat
the environment as a read-only block of memory, then the getenv()
result has a lifetime equivalent to the entire process.  And a quick
grep:

$ git grep '\b[sp][eu]tenv('
tests/tcg/multiarch/linux/linux-sigrtminmax.c:        setenv("QEMU_RTSIG_MAP", 
rt_sigmap, 0);
util/envlist.c: * than putenv(3).

shows that we aren't generally modifying qemu's environ.  Okay, there
ARE a few calls to unsetenv() in util/systemd.c, but that's the file
you're modifying, and before this patch it was already using getenv()
without worrying about copying its results.  We _do_ need to modify
the environment after reading it, at which point, you might be right
that calling getenv(), then unsetenv(), then relying on the former
pointer may be unsafe; but if this is the only file manipulating the
environment we have quite a bit of control over safety aspects, and
may still be able to design something that doesn't have to copy
getenv() results.

> 
> This proposed implementation uses a static buffer, and breaks socket
> activation with a visible error_report if LISTEN_FDNAMES is too large.
> Another approach would be to g_strdup the value returned by getenv,
> which would have failure modes on heap exhaustion, and would introduce
> a memory leak as there's no clear opportunity to g_free the copy.

A single g_strdup at process startup is not a memory leak, even if it
doesn't get explicitly freed, if you can't trigger it more than once.
An inability to g_free the copy is not fatal, as long as you are not
repeatedly duplicating the string each time a different caller wants
to also check what you already learned from getenv() on the first
caller.


> 
> Signed-off-by: Daniel Kahn Gillmor <d...@fifthhorseman.net>
> ---
>  util/systemd.c | 33 +++++++++++++++++++++++++++++----
>  1 file changed, 29 insertions(+), 4 deletions(-)

> 
> diff --git a/util/systemd.c b/util/systemd.c
> index ced518f771..1eca2bd69f 100644
> --- a/util/systemd.c
> +++ b/util/systemd.c
> @@ -16,37 +16,62 @@
>  #include "qemu/error-report.h"
>  
>  #ifndef _WIN32
> +static char fdnames[256];

Does systemd guarantee this particular size?  Should it be a #define?

> +
>  unsigned int check_socket_activation(void)
>  {
> +    static unsigned int nr_fds = -1;
>      const char *s;
>      unsigned long pid;
> -    unsigned long nr_fds;
> +    unsigned long nr_fdsl;
>      unsigned int i;
>      int fd;
>      int f;
>      int err;
>  
> +    if (nr_fds != -1) {
> +        return nr_fds;
> +    }
>      s = getenv("LISTEN_PID");
>      if (s == NULL) {
> +        nr_fds = 0;
>          return 0;
>      }
>      err = qemu_strtoul(s, NULL, 10, &pid);
>      if (err) {
> +        nr_fds = 0;
>          return 0;
>      }
>      if (pid != getpid()) {
> +        nr_fds = 0;
>          return 0;
>      }
>  
>      s = getenv("LISTEN_FDS");
>      if (s == NULL) {
> +        nr_fds = 0;
>          return 0;
>      }
> -    err = qemu_strtoul(s, NULL, 10, &nr_fds);
> +    err = qemu_strtoul(s, NULL, 10, &nr_fdsl);
>      if (err) {
> +        nr_fds = 0;
>          return 0;
>      }
> -    assert(nr_fds <= UINT_MAX);
> +    assert(nr_fdsl <= UINT_MAX);

I can put a value in the environment for LISTEN_FDS that will trip
this assertion, on a 64-bit platform.  While an early exit due to
garbage in the environment is okay, it should probably be cleaner than
by an assertion failure.

> +    nr_fds = (unsigned int) nr_fdsl;
> +    s = getenv("LISTEN_FDNAMES");
> +    if (s != NULL) {
> +        size_t fdnames_len = strlen(s);
> +        if (fdnames_len + 1 > sizeof(fdnames)) {
> +            error_report("LISTEN_FDNAMES is larger than %ldu bytes, "
> +                         "ignoring socket activation.",

error_report() should not use trailing '.'.  A g_strdup'd buffer won't
suffer from this arbitrary length limitation.

> +                         sizeof(fdnames));
> +            nr_fds = 0;
> +            return 0;
> +        } else {
> +            memcpy(fdnames, s, fdnames_len + 1);
> +        }
> +    }
>  
>      /* So these are not passed to any child processes we might start. */
>      unsetenv("LISTEN_FDS");
> @@ -69,7 +94,7 @@ unsigned int check_socket_activation(void)
>          }
>      }
>  
> -    return (unsigned int) nr_fds;
> +    return nr_fds;
>  }
>  
>  #else /* !_WIN32 */
> -- 
> 2.47.2
> 
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.
Virtualization:  qemu.org | libguestfs.org


Reply via email to