[PATCH] Core: fixed environment on exit better

Maxim Dounin mdounin at mdounin.ru
Sat Jun 24 02:06:46 UTC 2023


Hello!

On Fri, Jun 23, 2023 at 07:55:47PM +0400, Sergey Kandaurov wrote:

> # HG changeset patch
> # User Sergey Kandaurov <pluknet at nginx.com>
> # Date 1687535739 -14400
> #      Fri Jun 23 19:55:39 2023 +0400
> # Node ID 34a8e1a4161542896c11c4a5c60d6a6fe1931e3d
> # Parent  c681f4906d838ac45b517fada4c4274ade03fd3c
> Core: fixed environment on exit better.
> 
> The fix in 6822:c045b4926b2c to replace environment allocations from a pool
> was incomplete, such that it left pool owned allocations for variables set
> with values using the "env" directive.  Another consumer of this interface
> is the QUIC BPF helper that uses the environment variable NGINX_BPF_MAPS.
> The fix is to re-allocate variables with malloc, to avoid use after free.
> If memory allocation failed, the only option is to expel from environment.
> 
> The observed symptoms are similar to described in 6822:c045b4926b2c, that
> is a segmentation fault on worker process exit from atexit() handler seen
> with 3rd party modules or if nginx was built with lcov profiling.
> 
> diff --git a/src/core/nginx.c b/src/core/nginx.c
> --- a/src/core/nginx.c
> +++ b/src/core/nginx.c
> @@ -631,17 +631,48 @@ ngx_cleanup_environment(void *data)
>  {
>      char  **env = data;
>  
> -    if (environ == env) {
> +    char        *p, **penv;
> +    size_t       size;
> +    ngx_uint_t   i, n;
>  
> -        /*
> -         * if the environment is still used, as it happens on exit,
> -         * the only option is to leak it
> -         */
> -
> +    if (environ != env) {
> +        ngx_free(env);
>          return;
>      }

Note: if we assume that the environment can be arbitrary modified, 
I don't think it's enough.  For example, a setenv() call can 
replace environ with something different, so this code will free 
env and exit, but pool-allocated variables will be still 
referenced by the new environ.

This is probably out of the scope of this patch though.

>  
> -    ngx_free(env);
> +    /*
> +     * if the environment is still used, as it happens on exit,
> +     * the only option is to leak it
> +     */
> +
> +    for (n = 0; env[n]; n++) { }
> +
> +    for (i = 0; env[i]; i++) {
> +
> +        for (penv = ngx_os_environ; *penv; penv++) {
> +            if (ngx_strcmp(*penv, env[i]) == 0) {

I don't think it's going to work.  If I'm reading this correctly, 
with the initial environment "FOO=bar" and "env FOO=bar;" in the 
configuration this will assume the string is from ngx_os_environ, 
but it will be from ccf->env instead.

> +                break;
> +            }
> +        }
> +
> +        if (*penv) {
> +            continue;
> +        }
> +
> +        /* substitute pool allocated variables */
> +
> +        size = ngx_strlen(env[i]) + 1;
> +        p = malloc(size);

There should be no direct malloc() calls except in low-level code 
where logging is not available.  I don't think this particular 
place counts, so ngx_alloc() should be used instead.

> +
> +        if (p) {
> +            ngx_memcpy(p, env[i], size);
> +            env[i] = p;
> +
> +        } else {
> +            ngx_memmove(&env[i], &env[i + 1], sizeof(char *) * (n - i));
> +            i--; n--;
> +        }
> +    }
>  }

Overall, it looks like a better idea might be to simply add all 
the variables added to the environment to the environ allocation 
in ngx_set_environment().

In ngx_set_environment() we explicitly know if the particular 
variable is from OS environment or from ccf->environ, so there 
will be no need to guess.  Also, the appropriate log is readily 
available (and already used in the existing ngx_alloc() call), and 
the code to cleanup things is already there and will work without 
additional modifications.

-- 
Maxim Dounin
http://mdounin.ru/


More information about the nginx-devel mailing list