Linux Manual Pages development
 help / color / mirror / Atom feed
From: Alejandro Colomar <alx@kernel.org>
To: Jason Yundt <jason@jasonyundt.email>
Cc: linux-man@vger.kernel.org
Subject: Re: [PATCH v2] man/man7/environ.7: Fix underspecification of "name=value" strings
Date: Tue, 7 Jul 2026 13:31:31 +0200	[thread overview]
Message-ID: <akziEjm7cgrNTmGT@devuan> (raw)
In-Reply-To: <74bea4adebbd24979b9a072d518f1f202a788674.1783421176.git.jason@jasonyundt.email>

[-- Attachment #1: Type: text/plain, Size: 13090 bytes --]

Hi Jason,

On 2026-07-07T06:53:26-0400, Jason Yundt wrote:
> Before this change, environ(7) said this:
> 
> > By convention, the strings in environ have the form "name=value".  The
> > name is case-sensitive and may not contain the character "=".  The
> > value can be anything that can be represented as a string.  The name
> > and the value may not contain an embedded null byte ('\0'), since this
> > is assumed to terminate the string.
> 
> That description has a few problems:
> 
> 1. It talks about ‘the character "="’, but it doesn’t specify what
>    character encoding would be used to represent that character.  Two
>    different character encodings could represent that same “=” character
>    using two different bytes (or even sequences of bytes).
> 
> 2. It mentions that ‘The name is case-sensitive and may not contain the
>    character "=".’  It doesn’t clearly say what what is allowed to be in
>    a name.  It only says that those two things are explicitly
>    disallowed.
> 
> This change fixes those two problems.  For the first problem, this
> change makes it so that the description is all about bytes, not
> characters.  Describing the format in terms of bytes allows us to
> sidestep the question of character encoding entirely.  Additionally, it
> is more accurate to describe strings in environ as being sequences of
> bytes instead of sequences of characters.  Both the name and value of an
> environment variable could be sequences of bytes that don’t contain any
> characters at all.
> 
> For the second problem, this change clarifies that the name of an
> environment variable can contain any byte except for 0x3D.  It also
> clarifies that while it’s OK for environment variable values to be
> empty, it’s not OK for environment variable names to be empty.
> 
> Additionally, this change replaces "=" with '='.  In the C programming
> language, "=" refers to two bytes: one for the equals character plus one
> for the terminating null byte.  In the C programming language, '='
> refers to a single byte.  In this particular instance, we’re talking
> about a single byte, so it’s better to use '='.  Using '=' also makes
> environ(7) more internally consistent.  Before this change, environ(7)
> used '\0' and "=".  This change makes it so that environ(7) uses '\0'
> and '='.

I like the change from this paragraph.  If you send a separate patch
with only this change, I'll apply it.

> 
> I was able to obtain obtain the information that I needed in order to
> create this change by writing two test programs:
> 
> show-env-var-bytes.c:
> 
> 	#include <stdio.h>
> 	#include <stdlib.h>
> 
> 	void print_string_bytes(char *s) {
> 		printf("\"");
> 		for (size_t i = 0; s[i] != '\0'; i++) {
> 			printf("\\x%02hhX", s[i]);
> 		}
> 		printf("\"\n");
> 	}
> 
> 	int main(int argc, char *argv[]) {
> 		if (argc != 2) {
> 			fprintf(stderr, "USAGE: %s <NAME>\n", argv[0]);
> 			return EXIT_FAILURE;
> 		}
> 		printf("Name: ");
> 		print_string_bytes(argv[1]);
> 		char *getenv_result = getenv(argv[1]);
> 		if (getenv_result == NULL) {
> 			fprintf(stderr, "Environment variable not found.\n");
> 			return EXIT_FAILURE;
> 		}
> 		printf("Value: ");
> 		print_string_bytes(getenv_result);
> 	}
> 
> set-env-var-then-show-bytes.c:
> 
> 	#include <stdbool.h>
> 	#include <stdio.h>
> 	#include <stdlib.h>
> 	#include <string.h>
> 	#include <unistd.h>
> 
> 	char *every_nonnull_byte(bool exclude_3d_byte);
> 	char *environ_item(char *name, char *value);
> 
> 	char *every_nonnull_byte(bool exclude_3d_byte) {
> 		char           *ret;
> 		size_t         i = 0, size = 256;

Unused variable 'i'.

> 		unsigned char  byte = '\1';
> 
> 		if (exclude_3d_byte)
> 			size -= 1;
> 		ret = malloc(size);
> 		for (size_t i = 0; i < (size - 1); i++) {
> 			if (exclude_3d_byte && byte == '\x3D') {
> 				byte++;
> 			}
> 			ret[i] = byte;
> 			byte++;
> 		}
> 		ret[size - 1] = '\0';
> 		return ret;
> 	}
> 
> 	char *environ_item(char *name, char *value) {
> 		char  *ret = malloc(strlen(name) + 1 + strlen(value) + 1);
> 
> 		sprintf(ret, "%s\x3D%s", name, value);
> 		return ret;
> 	}
> 
> 	int main(int argc, char *argv[]) {
> 		char *name = every_nonnull_byte(true);
> 		char *value = every_nonnull_byte(false);
> 		char *env[] = { environ_item(name, value), NULL };
> 
> 		if (argc != 2) {
> 			fprintf(stderr, "USAGE: %s <PATH>\n", argv[0]);
> 			return EXIT_FAILURE;
> 		}
> 
> 		execle(argv[1], argv[1], name, NULL, env);
> 		perror("execle() failed");
> 		free(name);
> 		free(value);
> 		free(env[0]);
> 		return EXIT_FAILURE;
> 	}
> 
> Once those two programs are compiled you can run them by running this
> command:
> 
>     ./set-env-var-then-show-bytes ./show-env-var-bytes

I think these programs don't prove what happens on a system with an
encoding incompatible with ASCII.  I think if you compile glibc on an
EBDIC-based system (if that is possible at all, which I ignore), you'll
get the behavior based on '=' and not 0x3D.  I think this is required by
POSIX.  0x3D is not special.

Of course, if your system is a mix of ASCII and EBDIC, you have
a problem: glibc might have been compiled as ASCII (and thus use 0x3D)
and your program might be using something else.


Cheers,
Alex

> Additionally, I got the information about the setenv(3) and unsetenv(3)
> functions from their man pages (specifically, the parts of their man pages
> that talk about EINVAL).
> 
> Signed-off-by: Jason Yundt <jason@jasonyundt.email>
> ---
>  man/man7/environ.7 | 16 +++++++++++++---
>  1 file changed, 13 insertions(+), 3 deletions(-)
> 
> diff --git a/man/man7/environ.7 b/man/man7/environ.7
> index 31a69017cf75..89d48991e8b7 100644
> --- a/man/man7/environ.7
> +++ b/man/man7/environ.7
> @@ -28,9 +28,19 @@ .SH DESCRIPTION
>  .I environ
>  have the form
>  .RI \[dq] name\f[B]=\f[]value \[dq].
> -The name is case-sensitive and may not contain
> -the character
> -.RB \[dq] = \[dq].
> +The name is case-sensitive
> +and may contain any nonnull byte
> +other than 0x3D (the
> +.BR ascii (7)
> +.RB \[aq] = \[aq]
> +character).
> +The name must be at least one byte long,
> +or else programs will not be able to manipulate it using the
> +.BR setenv (3)
> +or
> +.BR unsetenv (3)
> +functions.
> +Immediately after the name, there must be a 0x3D byte.
>  The value can be anything that can be represented as a string.
>  The name and the value may not contain an embedded null byte (\[aq]\[rs]0\[aq]),
>  since this is assumed to terminate the string.
> 
> Range-diff against v1:
> 1:  d5b0d9b86029 ! 1:  74bea4adebbd man/man7/environ.7: Fix underspecification of "name=value" strings
>     @@ Commit message
>          and '='.
>      
>          I was able to obtain obtain the information that I needed in order to
>     -    create this change by writing a test program.  You can find the test
>     -    program here [1].  Additionally, I got the information about the setenv(3)
>     -    and unsetenv(3) functions from their man pages (specifically, the parts
>     -    of their man pages that talk about EINVAL).
>     +    create this change by writing two test programs:
>      
>     -    [1]: <https://codeberg.org/JasonYundt/environ-format-example-program>
>     +    show-env-var-bytes.c:
>     +
>     +            #include <stdio.h>
>     +            #include <stdlib.h>
>     +
>     +            void print_string_bytes(char *s) {
>     +                    printf("\"");
>     +                    for (size_t i = 0; s[i] != '\0'; i++) {
>     +                            printf("\\x%02hhX", s[i]);
>     +                    }
>     +                    printf("\"\n");
>     +            }
>     +
>     +            int main(int argc, char *argv[]) {
>     +                    if (argc != 2) {
>     +                            fprintf(stderr, "USAGE: %s <NAME>\n", argv[0]);
>     +                            return EXIT_FAILURE;
>     +                    }
>     +                    printf("Name: ");
>     +                    print_string_bytes(argv[1]);
>     +                    char *getenv_result = getenv(argv[1]);
>     +                    if (getenv_result == NULL) {
>     +                            fprintf(stderr, "Environment variable not found.\n");
>     +                            return EXIT_FAILURE;
>     +                    }
>     +                    printf("Value: ");
>     +                    print_string_bytes(getenv_result);
>     +            }
>     +
>     +    set-env-var-then-show-bytes.c:
>     +
>     +            #include <stdbool.h>
>     +            #include <stdio.h>
>     +            #include <stdlib.h>
>     +            #include <string.h>
>     +            #include <unistd.h>
>     +
>     +            char *every_nonnull_byte(bool exclude_3d_byte);
>     +            char *environ_item(char *name, char *value);
>     +
>     +            char *every_nonnull_byte(bool exclude_3d_byte) {
>     +                    char           *ret;
>     +                    size_t         i = 0, size = 256;
>     +                    unsigned char  byte = '\1';
>     +
>     +                    if (exclude_3d_byte)
>     +                            size -= 1;
>     +                    ret = malloc(size);
>     +                    for (size_t i = 0; i < (size - 1); i++) {
>     +                            if (exclude_3d_byte && byte == '\x3D') {
>     +                                    byte++;
>     +                            }
>     +                            ret[i] = byte;
>     +                            byte++;
>     +                    }
>     +                    ret[size - 1] = '\0';
>     +                    return ret;
>     +            }
>     +
>     +            char *environ_item(char *name, char *value) {
>     +                    char  *ret = malloc(strlen(name) + 1 + strlen(value) + 1);
>     +
>     +                    sprintf(ret, "%s\x3D%s", name, value);
>     +                    return ret;
>     +            }
>     +
>     +            int main(int argc, char *argv[]) {
>     +                    char *name = every_nonnull_byte(true);
>     +                    char *value = every_nonnull_byte(false);
>     +                    char *env[] = { environ_item(name, value), NULL };
>     +
>     +                    if (argc != 2) {
>     +                            fprintf(stderr, "USAGE: %s <PATH>\n", argv[0]);
>     +                            return EXIT_FAILURE;
>     +                    }
>     +
>     +                    execle(argv[1], argv[1], name, NULL, env);
>     +                    perror("execle() failed");
>     +                    free(name);
>     +                    free(value);
>     +                    free(env[0]);
>     +                    return EXIT_FAILURE;
>     +            }
>     +
>     +    Once those two programs are compiled you can run them by running this
>     +    command:
>     +
>     +        ./set-env-var-then-show-bytes ./show-env-var-bytes
>     +
>     +    Additionally, I got the information about the setenv(3) and unsetenv(3)
>     +    functions from their man pages (specifically, the parts of their man pages
>     +    that talk about EINVAL).
>      
>          Signed-off-by: Jason Yundt <jason@jasonyundt.email>
>      
>     @@ man/man7/environ.7: .SH DESCRIPTION
>      -The name is case-sensitive and may not contain
>      -the character
>      -.RB \[dq] = \[dq].
>     --The value can be anything that can be represented as a string.
>     --The name and the value may not contain an embedded null byte (\[aq]\[rs]0\[aq]),
>     --since this is assumed to terminate the string.
>      +The name is case-sensitive
>     -+and may contain any byte
>     -+other than null (\[aq]\[rs]0\[aq]) and 0x3D (the
>     ++and may contain any nonnull byte
>     ++other than 0x3D (the
>      +.BR ascii (7)
>      +.RB \[aq] = \[aq]
>      +character).
>     @@ man/man7/environ.7: .SH DESCRIPTION
>      +or
>      +.BR unsetenv (3)
>      +functions.
>     -+Immediately after the name, there should be a 0x3D byte.
>     -+Immediately after the 0x3D byte is the value.
>     -+The value may contain any byte except for null.
>     -+The value may be zero bytes long.
>     -+Immediately after the value, there must be a terminating null byte.
>     - .P
>     - Environment variables may be placed in the shell's environment by the
>     - .I export
>     ++Immediately after the name, there must be a 0x3D byte.
>     + The value can be anything that can be represented as a string.
>     + The name and the value may not contain an embedded null byte (\[aq]\[rs]0\[aq]),
>     + since this is assumed to terminate the string.
> -- 
> 2.54.0
> 

-- 
<https://www.alejandro-colomar.es>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2026-07-07 11:31 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-06 14:26 [PATCH v1] man/man7/environ.7: Fix underspecification of "name=value" strings Jason Yundt
2026-07-06 15:27 ` Alejandro Colomar
2026-07-07 10:54   ` Jason Yundt
2026-07-07 11:23     ` Alejandro Colomar
2026-07-07 10:53 ` [PATCH v2] " Jason Yundt
2026-07-07 11:31   ` Alejandro Colomar [this message]
2026-07-07 13:39     ` G. Branden Robinson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=akziEjm7cgrNTmGT@devuan \
    --to=alx@kernel.org \
    --cc=jason@jasonyundt.email \
    --cc=linux-man@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox