[Bug 268479] lib/libc/stdlib/getenv.c may have a problem with putenv()

From: <bugzilla-noreply_at_freebsd.org>
Date: Tue, 20 Dec 2022 03:23:54 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=268479

            Bug ID: 268479
           Summary: lib/libc/stdlib/getenv.c may have a problem with
                    putenv()
           Product: Base System
           Version: CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Only Me
          Priority: ---
         Component: standards
          Assignee: standards@FreeBSD.org
          Reporter: dclarke@blastwave.org

I am not sure this is a bug or simply expected behavior. However I see
strange results when I attempt to override the uname(3) struct members
with env vars such as UNAME_s if I use putenv 'UNAME_s=' for an empty
value.

Looking at lib/libc/stdlib/getenv.c I see : 

000644          /* Create environment entry. */
000645          envVars[envNdx].name = string;
000646          envVars[envNdx].nameLen = -1;
000647          envVars[envNdx].value = NULL;
000648          envVars[envNdx].valueSize = -1;
000649          envVars[envNdx].putenv = true;
000650          envVars[envNdx].active = true;
000651          newEnvActive++;

Which seems to be fine with the insert of an env var of zero length.
However a zero length env var named UNAME_s will destroy the system
name.

What I see in a few simple tests : 

    (1) Trivial unsetenv 

styx$ cat uname_unsetenv.c
/*
 * uname_unsetenv.c Demonstrate that FreeBSD seems to allow env var
 *                  values to override the uname(3) struct members.
 *                  We may remove the env var contents with unsetenv.
 *
 * Copyright (C) Dennis Clarke 2022
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 *
 * https://www.gnu.org/licenses/gpl-3.0.txt
 */

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *
 *    An XSI-conforming application should ensure that the feature
 *    test macro _XOPEN_SOURCE is defined with the value 600 before
 *    inclusion of any header. This is needed to enable the
 *    functionality described in The _POSIX_C_SOURCE Feature Test
 *    Macro and in addition to enable the XSI extension.
 *
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>

int main(int argc, char *argv[])
{

    int j;
    struct utsname uname_data;
    char *env_var_value = NULL;

    /* tricky stuff about UNAME(3) :
     *
     *    These ENVIRONMENT variables override some uname struct members
     *
     *         env name     overrides
     *         --------------------------
     *         UNAME_s      sysname
     *         UNAME_r      release
     *         UNAME_v      version
     *         UNAME_m      machine
     *
     */
    char *env_var[] = {"UNAME_s","UNAME_r","UNAME_v","UNAME_m"};

    setlocale( LC_MESSAGES, "C" );

    /* check for and then unset those env vars */
    errno = 0;
    for ( j=0; j<4 ; j++ ) {
        env_var_value = getenv(env_var[j]);
        if ( env_var_value != NULL) {

            fprintf(stderr, "INFO : env var \"%s\" set to \"%s\"\n",
                    env_var[j], env_var_value);

            if (unsetenv(env_var[j]) < 0) {
                fprintf(stderr, "FAIL : could not clear env \"%s\"\n",
                        env_var[j]);

                perror("FAIL : ");
                return EXIT_FAILURE;
            } else {
                fprintf(stderr, "     : cleared env var \"%s\"\n", env_var[j]);
            }
        }
    }

    if ( uname( &uname_data ) < 0 ) {
        fprintf(stderr,
                 "WARNING : Could not attain system uname data.\n" );
        perror ("uname" );
    } else {
        printf("-------------------------------" );
        printf("------------------------------\n" );
        printf("        system name = %s\n", uname_data.sysname );
        printf("          node name = %s\n", uname_data.nodename );
        printf("            release = %s\n", uname_data.release );
        printf("            version = %s\n", uname_data.version );
        printf("            machine = %s\n", uname_data.machine );
        printf ( "-------------------------------" );
        printf ( "------------------------------" );
    }
    printf ("\n");

    return EXIT_SUCCESS;

}

Here is the system uname data :

styx$ uname -apKU 
FreeBSD styx 14.0-CURRENT FreeBSD 14.0-CURRENT #0 main-n259756-6692670f58f9:
Mon Dec 19 16:48:48 UTC 2022    
root@styx:/usr/obj/usr/src/amd64.amd64/sys/GENERIC amd64 amd64 1400075 1400075

styx$ ./uname_unsetenv
-------------------------------------------------------------
        system name = FreeBSD
          node name = styx
            release = 14.0-CURRENT
            version = FreeBSD 14.0-CURRENT #0 main-n259756-6692670f58f9: Mon
Dec 19 16:48:48 UTC 2022     root@styx:/usr/obj/usr/src/amd64.amd64/sys/GENERIC
            machine = amd64
-------------------------------------------------------------
styx$ 


We can pollute the uname struct members in FreeBSD : 

styx$ UNAME_s=system  UNAME_m=alpha64  UNAME_r=random  uname -apKU
system styx random FreeBSD 14.0-CURRENT #0 main-n259756-6692670f58f9: Mon Dec
19 16:48:48 UTC 2022     root@styx:/usr/obj/usr/src/amd64.amd64/sys/GENERIC
alpha64 amd64 1400075 1400075

The above code will remove the offending env vars : 

styx$ UNAME_s=system  UNAME_m=alpha64  UNAME_r=random ./uname_unsetenv 
INFO : env var "UNAME_s" set to "system"
     : cleared env var "UNAME_s"
INFO : env var "UNAME_r" set to "random"
     : cleared env var "UNAME_r"
INFO : env var "UNAME_m" set to "alpha64"
     : cleared env var "UNAME_m"
-------------------------------------------------------------
        system name = FreeBSD
          node name = styx
            release = 14.0-CURRENT
            version = FreeBSD 14.0-CURRENT #0 main-n259756-6692670f58f9: Mon
Dec 19 16:48:48 UTC 2022     root@styx:/usr/obj/usr/src/amd64.amd64/sys/GENERIC
            machine = amd64
-------------------------------------------------------------
styx$ 


Therefore the uname struct members are once again available to a given
process and we can determine inside an exec the system we are running
on from uname.



    (2) Try to use putenv to clear the offending env vars

styx$ cat uname_putenv.c
/*
 * uname_putenv.c   Demonstrate that FreeBSD seems to allow env var
 *                  values to override the uname(3) struct members.
 *                  We may change the env var contents with putenv
 *                  however putenv does not seem to work on the
 *                  second call.
 *
 * Copyright (C) Dennis Clarke 2022
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 *
 * https://www.gnu.org/licenses/gpl-3.0.txt
 */

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *
 *    An XSI-conforming application should ensure that the feature
 *    test macro _XOPEN_SOURCE is defined with the value 600 before
 *    inclusion of any header. This is needed to enable the
 *    functionality described in The _POSIX_C_SOURCE Feature Test
 *    Macro and in addition to enable the XSI extension.
 *
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>

int main(int argc, char *argv[])
{

    int j;
    struct utsname uname_data;
    char *env_var_value = NULL;

    /* tricky stuff about UNAME(3) :
     *
     *    These ENVIRONMENT variables override some uname struct members
     *
     *         env name     overrides
     *         --------------------------
     *         UNAME_s      sysname
     *         UNAME_r      release
     *         UNAME_v      version
     *         UNAME_m      machine
     *
     */
    char *env_var[] = {"UNAME_s","UNAME_r","UNAME_v","UNAME_m"};

    /* These are likely not needed however the sources 
     * for putenv seem to check for the '=' character
     * as well as the degenerate case where the submitted
     * string is merely the '=' char. So these make it
     * trivial to putenv an empty string. */
    char *env_var_to_clear[] = {"UNAME_s=","UNAME_r=","UNAME_v=","UNAME_m="};

    setlocale( LC_MESSAGES, "C" );

    /* check for and then unset those env vars */
    errno = 0;
    for ( j=0; j<4 ; j++ ) {
        env_var_value = getenv(env_var[j]);
        if ( env_var_value != NULL) {

            fprintf(stderr, "INFO : env var \"%s\" set to \"%s\"\n",
                    env_var[j], env_var_value);

            if (putenv(env_var_to_clear[j]) < 0) {
                fprintf(stderr, "FAIL : could not clear env \"%s\"\n",
                        env_var[j]);

                perror("FAIL : ");
                return EXIT_FAILURE;
            } else {
                fprintf(stderr, "     : cleared env var \"%s\"\n", env_var[j]);
            }
        }
    }

    if ( uname( &uname_data ) < 0 ) {
        fprintf(stderr,
                 "WARNING : Could not attain system uname data.\n" );
        perror ("uname" );
    } else {
        printf("-------------------------------" );
        printf("------------------------------\n" );
        printf("        system name = %s\n", uname_data.sysname );
        printf("          node name = %s\n", uname_data.nodename );
        printf("            release = %s\n", uname_data.release );
        printf("            version = %s\n", uname_data.version );
        printf("            machine = %s\n", uname_data.machine );
        printf ( "-------------------------------" );
        printf ( "------------------------------" );
    }
    printf ("\n");

    return EXIT_SUCCESS;

}

styx$ 

First we have the actual system : 

styx$ uname -apKU                                                               
but why this styx 14.0-CURRENT FreeBSD 14.0-CURRENT #0
main-n259756-6692670f58f9: Mon Dec 19 16:48:48 UTC 2022    
root@styx:/usr/obj/usr/src/amd64.amd64/sys/GENERIC amd64 amd64 1400075 1400075
styx$ 

However we can wreck havok with the uname members : 

styx$ UNAME_s=system  UNAME_m=alpha64  UNAME_r=random  UNAME_v=NOTHING   uname
-apKU  
system styx random NOTHING alpha64 amd64 1400075 1400075
styx$ 

Worse, there seems to be no way to clear those values with putenv : 

styx$ 
styx$ UNAME_s=system  UNAME_m=alpha64  UNAME_r=random  UNAME_v=NOTHING  
./uname_putenv
INFO : env var "UNAME_s" set to "system"
     : cleared env var "UNAME_s"
INFO : env var "UNAME_r" set to "random"
     : cleared env var "UNAME_r"
INFO : env var "UNAME_v" set to "NOTHING"
     : cleared env var "UNAME_v"
INFO : env var "UNAME_m" set to "alpha64"
     : cleared env var "UNAME_m"
-------------------------------------------------------------
        system name = 
          node name = styx
            release = 
            version = 
            machine = 
-------------------------------------------------------------
styx$ 

So the question would be, should putenv() allow the creation of these
env vars as zero length strings?  I tested on a few other systems but
it seems only FreeBSD allows these UNAME_foo env vars to override the
uname struct members. Should putenv create zero length data for these
env vars?


Dennis Clarke
RISC-V/SPARC/PPC/ARM/CISC
UNIX and Linux spoken
GreyBeard and suspenders optional

-- 
You are receiving this mail because:
You are the assignee for the bug.