From fbd37f04c5aebc06ab2a2066fd56787a4c71820a Mon Sep 17 00:00:00 2001
From: Filip Schauer <f.schauer@proxmox.com>
Date: Mon, 8 Sep 2025 11:11:31 +0200
Subject: [PATCH 1/3] conf: split `lxc.environment` into `runtime` and `hooks`

Introduce `lxc.environment.runtime` to set environment variables only
for the container init process and `lxc.environment.hooks` to set
environment variables only for hooks. Leave the original
`lxc.environment` unchanged. It still applies to everything.

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
 src/lxc/attach.c  |  6 +++-
 src/lxc/conf.c    | 14 +++++----
 src/lxc/conf.h    | 15 +++++++---
 src/lxc/confile.c | 72 +++++++++++++++++++++++++++++++++++++++++------
 src/lxc/start.c   | 12 ++++++--
 5 files changed, 98 insertions(+), 21 deletions(-)

diff --git a/src/lxc/attach.c b/src/lxc/attach.c
index 8f2f7a37c3..f22f83f0df 100644
--- a/src/lxc/attach.c
+++ b/src/lxc/attach.c
@@ -879,7 +879,11 @@ static int lxc_attach_set_environment(struct attach_context *ctx,
 
 	/* Set container environment variables.*/
 	if (ctx->container->lxc_conf) {
-		ret = lxc_set_environment(ctx->container->lxc_conf);
+		ret = lxc_set_environment(&ctx->container->lxc_conf->environment);
+		if (ret < 0)
+			return -1;
+
+		ret = lxc_set_environment(&ctx->container->lxc_conf->environment_runtime);
 		if (ret < 0)
 			return -1;
 	}
diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index 1899b2806d..7533e28309 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -3209,6 +3209,8 @@ struct lxc_conf *lxc_conf_init(void)
 	new->root_nsuid_map = NULL;
 	new->root_nsgid_map = NULL;
 	INIT_LIST_HEAD(&new->environment);
+	INIT_LIST_HEAD(&new->environment_runtime);
+	INIT_LIST_HEAD(&new->environment_hooks);
 	INIT_LIST_HEAD(&new->limits);
 	INIT_LIST_HEAD(&new->sysctls);
 	INIT_LIST_HEAD(&new->procs);
@@ -4239,18 +4241,18 @@ int lxc_clear_groups(struct lxc_conf *c)
 	return 0;
 }
 
-int lxc_clear_environment(struct lxc_conf *c)
+int lxc_clear_environment(struct list_head *environment)
 {
 	struct environment_entry *env, *nenv;
 
-	list_for_each_entry_safe(env, nenv, &c->environment, head) {
+	list_for_each_entry_safe(env, nenv, environment, head) {
 		list_del(&env->head);
 		free(env->key);
 		free(env->val);
 		free(env);
 	}
 
-	INIT_LIST_HEAD(&c->environment);
+	INIT_LIST_HEAD(environment);
 	return 0;
 }
 
@@ -4359,7 +4361,7 @@ void lxc_conf_free(struct lxc_conf *conf)
 	lxc_clear_mount_entries(conf);
 	lxc_clear_idmaps(conf);
 	lxc_clear_groups(conf);
-	lxc_clear_environment(conf);
+	lxc_clear_environment(&conf->environment);
 	lxc_clear_limits(conf, "lxc.prlimit");
 	lxc_clear_sysctls(conf, "lxc.sysctl");
 	lxc_clear_procs(conf, "lxc.proc");
@@ -5210,11 +5212,11 @@ void suggest_default_idmap(void)
 	ERROR("lxc.idmap = g 0 %u %u", gid, grange);
 }
 
-int lxc_set_environment(const struct lxc_conf *conf)
+int lxc_set_environment(const struct list_head *environment)
 {
 	struct environment_entry *env;
 
-	list_for_each_entry(env, &conf->environment, head) {
+	list_for_each_entry(env, environment, head) {
 		int ret;
 
 		ret = setenv(env->key, env->val, 1);
diff --git a/src/lxc/conf.h b/src/lxc/conf.h
index 31cd39e3f7..762d58901d 100644
--- a/src/lxc/conf.h
+++ b/src/lxc/conf.h
@@ -506,10 +506,17 @@ struct lxc_conf {
 	unsigned int monitor_unshare;
 	unsigned int monitor_signal_pdeath;
 
-	/* list of environment variables we'll add to the container when
-	 * started */
+	/* list of environment variables to provide to both the container's init
+	 * process and hooks */
 	struct list_head environment;
 
+	/* list of environment variables to provide to the container's init
+	 * process */
+	struct list_head environment_runtime;
+
+	/* list of environment variables to provide to container hooks */
+	struct list_head environment_hooks;
+
 	/* text representation of the config file */
 	char *unexpanded_config;
 	size_t unexpanded_len;
@@ -599,7 +606,7 @@ __hidden extern int lxc_clear_automounts(struct lxc_conf *c);
 __hidden extern int lxc_clear_hooks(struct lxc_conf *c, const char *key);
 __hidden extern int lxc_clear_idmaps(struct lxc_conf *c);
 __hidden extern int lxc_clear_groups(struct lxc_conf *c);
-__hidden extern int lxc_clear_environment(struct lxc_conf *c);
+__hidden extern int lxc_clear_environment(struct list_head *environment);
 __hidden extern int lxc_clear_limits(struct lxc_conf *c, const char *key);
 __hidden extern int lxc_delete_autodev(struct lxc_handler *handler);
 __hidden extern int lxc_clear_autodev_tmpfs_size(struct lxc_conf *c);
@@ -710,7 +717,7 @@ static inline int lxc_personality(personality_t persona)
 	return personality(persona);
 }
 
-__hidden extern int lxc_set_environment(const struct lxc_conf *conf);
+__hidden extern int lxc_set_environment(const struct list_head *environment);
 __hidden extern int parse_cap(const char *cap_name, __u32 *cap);
 
 #endif /* __LXC_CONF_H */
diff --git a/src/lxc/confile.c b/src/lxc/confile.c
index 5045741bb0..7163cd1b22 100644
--- a/src/lxc/confile.c
+++ b/src/lxc/confile.c
@@ -80,6 +80,8 @@ lxc_config_define(console_rotate);
 lxc_config_define(console_size);
 lxc_config_define(unsupported_key);
 lxc_config_define(environment);
+lxc_config_define(environment_runtime);
+lxc_config_define(environment_hooks);
 lxc_config_define(ephemeral);
 lxc_config_define(execute_cmd);
 lxc_config_define(group);
@@ -211,6 +213,8 @@ static struct lxc_config_t config_jump_table[] = {
 	{ "lxc.console.rotate",             true,  set_config_console_rotate,             get_config_console_rotate,             clr_config_console_rotate,             },
 	{ "lxc.console.size",               true,  set_config_console_size,               get_config_console_size,               clr_config_console_size,               },
 	{ "lxc.sched.core",		    true,  set_config_sched_core,		  get_config_sched_core,                 clr_config_sched_core,                 },
+	{ "lxc.environment.runtime",        true,  set_config_environment_runtime,        get_config_environment_runtime,        clr_config_environment_runtime         },
+	{ "lxc.environment.hooks",          true,  set_config_environment_hooks,          get_config_environment_hooks,          clr_config_environment_hooks           },
 	{ "lxc.environment",                true,  set_config_environment,                get_config_environment,                clr_config_environment,                },
 	{ "lxc.ephemeral",                  true,  set_config_ephemeral,                  get_config_ephemeral,                  clr_config_ephemeral,                  },
 	{ "lxc.execute.cmd",                true,  set_config_execute_cmd,                get_config_execute_cmd,                clr_config_execute_cmd,                },
@@ -1574,15 +1578,15 @@ static int set_config_group(const char *key, const char *value,
 	return 0;
 }
 
-static int set_config_environment(const char *key, const char *value,
-				  struct lxc_conf *lxc_conf, void *data)
+static int set_config_environment_impl(const char *value,
+				       struct list_head *environment)
 {
 	__do_free char *dup = NULL, *val = NULL;
 	__do_free struct environment_entry *new_env = NULL;
 	char *env_val;
 
 	if (lxc_config_value_empty(value))
-		return lxc_clear_environment(lxc_conf);
+		return lxc_clear_environment(environment);
 
 	new_env = zalloc(sizeof(struct environment_entry));
 	if (!new_env)
@@ -1609,12 +1613,30 @@ static int set_config_environment(const char *key, const char *value,
 	new_env->key = move_ptr(dup);
 	new_env->val = move_ptr(val);
 
-	list_add_tail(&new_env->head, &lxc_conf->environment);
+	list_add_tail(&new_env->head, environment);
 	move_ptr(new_env);
 
 	return 0;
 }
 
+static int set_config_environment(const char *key, const char *value,
+				  struct lxc_conf *lxc_conf, void *data)
+{
+	return set_config_environment_impl(value, &lxc_conf->environment);
+}
+
+static int set_config_environment_runtime(const char *key, const char* value,
+					  struct lxc_conf *lxc_conf, void *data)
+{
+	return set_config_environment_impl(value, &lxc_conf->environment_runtime);
+}
+
+static int set_config_environment_hooks(const char *key, const char* value,
+					struct lxc_conf *lxc_conf, void *data)
+{
+	return set_config_environment_impl(value, &lxc_conf->environment_hooks);
+}
+
 static int set_config_tty_max(const char *key, const char *value,
 			      struct lxc_conf *lxc_conf, void *data)
 {
@@ -4473,8 +4495,8 @@ static int get_config_group(const char *key, char *retv, int inlen,
 	return fulllen;
 }
 
-static int get_config_environment(const char *key, char *retv, int inlen,
-				  struct lxc_conf *c, void *data)
+static int get_config_environment_impl(char *retv, int inlen,
+				       struct list_head *environment)
 {
 	int len, fulllen = 0;
 	struct environment_entry *env;
@@ -4484,13 +4506,32 @@ static int get_config_environment(const char *key, char *retv, int inlen,
 	else
 		memset(retv, 0, inlen);
 
-	list_for_each_entry(env, &c->environment, head) {
+	list_for_each_entry(env, environment, head) {
 		strprint(retv, inlen, "%s=%s\n", env->key, env->val);
 	}
 
 	return fulllen;
 }
 
+static int get_config_environment(const char *key, char *retv, int inlen,
+				  struct lxc_conf *c, void *data)
+{
+	return get_config_environment_impl(retv, inlen, &c->environment);
+}
+
+static int get_config_environment_runtime(const char *key, char *retv,
+					  int inlen, struct lxc_conf *c,
+					  void *data)
+{
+	return get_config_environment_impl(retv, inlen, &c->environment_runtime);
+}
+
+static int get_config_environment_hooks(const char *key, char *retv, int inlen,
+					struct lxc_conf *c, void *data)
+{
+	return get_config_environment_impl(retv, inlen, &c->environment_hooks);
+}
+
 static int get_config_execute_cmd(const char *key, char *retv, int inlen,
 			       struct lxc_conf *c, void *data)
 {
@@ -5211,7 +5252,19 @@ static inline int clr_config_group(const char *key, struct lxc_conf *c,
 static inline int clr_config_environment(const char *key, struct lxc_conf *c,
 					 void *data)
 {
-	return lxc_clear_environment(c);
+	return lxc_clear_environment(&c->environment);
+}
+
+static inline int clr_config_environment_runtime(const char *key,
+						 struct lxc_conf *c, void *data)
+{
+	return lxc_clear_environment(&c->environment_runtime);
+}
+
+static inline int clr_config_environment_hooks(const char *key,
+					       struct lxc_conf *c, void *data)
+{
+	return lxc_clear_environment(&c->environment_hooks);
 }
 
 static inline int clr_config_execute_cmd(const char *key, struct lxc_conf *c,
@@ -6607,6 +6660,9 @@ int lxc_list_subkeys(struct lxc_conf *conf, const char *key, char *retv,
 	} else if (strequal(key, "lxc.console")) {
 		strprint(retv, inlen, "logfile\n");
 		strprint(retv, inlen, "path\n");
+	} else if (strequal(key, "lxc.environment")) {
+		strprint(retv, inlen, "runtime\n");
+		strprint(retv, inlen, "hooks\n");
 	} else if (strequal(key, "lxc.seccomp")) {
 		strprint(retv, inlen, "profile\n");
 	} else if (strequal(key, "lxc.signal")) {
diff --git a/src/lxc/start.c b/src/lxc/start.c
index d566dd5ddb..b8559bbebf 100644
--- a/src/lxc/start.c
+++ b/src/lxc/start.c
@@ -1451,7 +1451,11 @@ static int do_start(void *data)
 	 * to allow them to be used by the various hooks, such as the start
 	 * hook below.
 	 */
-	ret = lxc_set_environment(handler->conf);
+	ret = lxc_set_environment(&handler->conf->environment);
+	if (ret < 0)
+		goto out_warn_father;
+
+	ret = lxc_set_environment(&handler->conf->environment_hooks);
 	if (ret < 0)
 		goto out_warn_father;
 
@@ -1552,7 +1556,11 @@ static int do_start(void *data)
 	if (ret < 0)
 		SYSERROR("Failed to clear environment.");
 
-	ret = lxc_set_environment(handler->conf);
+	ret = lxc_set_environment(&handler->conf->environment);
+	if (ret < 0)
+		goto out_warn_father;
+
+	ret = lxc_set_environment(&handler->conf->environment_runtime);
 	if (ret < 0)
 		goto out_warn_father;
 

From 9ebe1f11ec66ec6df2b76abc089692e8e324b02b Mon Sep 17 00:00:00 2001
From: Filip Schauer <f.schauer@proxmox.com>
Date: Tue, 16 Sep 2025 11:38:11 +0200
Subject: [PATCH 2/3] api_extensions: add environment_runtime_hooks extension

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
 doc/api-extensions.md    | 7 +++++++
 src/lxc/api_extensions.h | 1 +
 2 files changed, 8 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 0e9c724e82..4a0699289c 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -160,3 +160,10 @@ This adds the new options `cgroup2`, `cgroup2:ro`, `cgroup2:force`,
 `cgroup2:ro:force` for the `lxc.mount.auto` configuration key. For example, if
 a user specifies `cgroup2:force` LXC will pre-mount a pure `cgroup2` layout for
 the container even if the host is running with a hybrid layout.
+
+## environment\_runtime\_hooks
+
+This introduces `lxc.environment.runtime` and `lxc.environment.hooks`
+configuration keys to allow environment variables to be applied only to the
+container init process or only to hooks respectively.
+`lxc.environment` remains and still applies to both.
diff --git a/src/lxc/api_extensions.h b/src/lxc/api_extensions.h
index 6a0b9fe98d..54ca565f86 100644
--- a/src/lxc/api_extensions.h
+++ b/src/lxc/api_extensions.h
@@ -52,6 +52,7 @@ static char *api_extensions[] = {
 	"idmapped_mounts_v2",
 	"core_scheduling",
 	"cgroup2_auto_mounting",
+	"environment_runtime_hooks",
 };
 
 static size_t nr_api_extensions = sizeof(api_extensions) / sizeof(*api_extensions);

From e0290fa4aa173f8e5a43adac4e345310bf82ef0f Mon Sep 17 00:00:00 2001
From: Filip Schauer <f.schauer@proxmox.com>
Date: Tue, 16 Sep 2025 11:52:27 +0200
Subject: [PATCH 3/3] doc: add lxc.environment.{runtime, hooks}

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
 doc/lxc.container.conf.sgml.in | 36 +++++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/doc/lxc.container.conf.sgml.in b/doc/lxc.container.conf.sgml.in
index 20ae63fd42..39efffbe56 100644
--- a/doc/lxc.container.conf.sgml.in
+++ b/doc/lxc.container.conf.sgml.in
@@ -2990,7 +2990,14 @@
       </para>
 
       <para>
-        This configuration parameter can be specified multiple times; once
+        Subkeys are available to narrow the scope of environment variables:
+        <command>lxc.environment.runtime</command> applies only to
+        the container's init process (and all its descendents),
+        and <command>lxc.environment.hooks</command> applies only to hooks.
+      </para>
+
+      <para>
+        These configuration parameters can be specified multiple times; once
         for each environment variable you wish to configure.
       </para>
 
@@ -3001,8 +3008,8 @@
           </term>
           <listitem>
             <para>
-              Specify an environment variable to pass into the container.
-              Example:
+              Environment variables applied both to the container init process
+              and to hooks. Example:
             </para>
             <programlisting>
               lxc.environment = APP_ENV=production
@@ -3017,6 +3024,29 @@
             </programlisting>
           </listitem>
         </varlistentry>
+
+        <varlistentry>
+          <term>
+            <option>lxc.environment.runtime</option>
+          </term>
+          <listitem>
+            <para>
+              Environment variables applied only to the container's init
+              process.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+            <option>lxc.environment.hooks</option>
+          </term>
+          <listitem>
+            <para>
+              Environment variables applied only to hooks.
+            </para>
+          </listitem>
+        </varlistentry>
       </variablelist>
     </refsect2>
 
