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 --]
next prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.