From 273a0b6ac98624532cfaa106646bc6e11279cce0 Mon Sep 17 00:00:00 2001 From: Xu Jihui Date: Thu, 19 Feb 2026 17:13:43 +0100 Subject: [PATCH 1/2] cgroups/cgfsng: fix whitespace errors in __cg_mount_direct Signed-off-by: Xu Jihui Signed-off-by: Alexander Mikhalitsyn --- src/cgroups/cgfsng.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cgroups/cgfsng.c b/src/cgroups/cgfsng.c index 562af3f29..849b4407b 100644 --- a/src/cgroups/cgfsng.c +++ b/src/cgroups/cgfsng.c @@ -399,21 +399,21 @@ static void trim(char *s) */ static int __cg_mount_direct(struct hierarchy *h, const char *controllerpath) { - __do_free char *controllers = NULL; - char *fstype = "cgroup2"; - unsigned long flags = 0; - int ret; - - flags |= MS_NOSUID; - flags |= MS_NOEXEC; - flags |= MS_NODEV; - flags |= MS_RELATIME; - - if (h->version != CGROUP2_SUPER_MAGIC) { - controllers = lxc_string_join(",", (const char **)h->controllers, false); - if (!controllers) - return -ENOMEM; - fstype = "cgroup"; + __do_free char *controllers = NULL; + char *fstype = "cgroup2"; + unsigned long flags = 0; + int ret; + + flags |= MS_NOSUID; + flags |= MS_NOEXEC; + flags |= MS_NODEV; + flags |= MS_RELATIME; + + if (h->version != CGROUP2_SUPER_MAGIC) { + controllers = lxc_string_join(",", (const char **)h->controllers, false); + if (!controllers) + return -ENOMEM; + fstype = "cgroup"; } ret = mount("cgroup", controllerpath, fstype, flags, controllers); From b7efa0592b32af6040615bb3dd2ecf8d655ead72 Mon Sep 17 00:00:00 2001 From: Xu Jihui Date: Fri, 20 Feb 2026 13:54:39 +0100 Subject: [PATCH 2/2] cgroups/cgfsng: do not change host-wide cgroup2 superblock options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when LXCFS daemon starts it creates an isolated mount namespace and mounts its own cgroup2 mount. The problem is that cgroup2 implementation in the kernel has a catch. cgroup2-fs superblock-level mount options can be changed if privileged user in init_cgroup_ns mounts a new cgroup2 mount and specifies superblock options different from ones specified earlier. See: https://github.com/torvalds/linux/blob/18f7fcd5e69a04df57b563360b88be72471d6b62/kernel/cgroup/cgroup.c#L2047 Fixes: #681 Reported-by: Emir Buljubašić Signed-off-by: Xu Jihui [ alex: significant modifications and refactoring ] Co-authored-by: Alexander Mikhalitsyn Signed-off-by: Alexander Mikhalitsyn --- src/cgroups/cgfsng.c | 17 +++++++- src/cgroups/cgroup_utils.c | 86 ++++++++++++++++++++++++++++++++++++++ src/cgroups/cgroup_utils.h | 2 + 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/cgroups/cgfsng.c b/src/cgroups/cgfsng.c index 849b4407b..6f392d929 100644 --- a/src/cgroups/cgfsng.c +++ b/src/cgroups/cgfsng.c @@ -414,9 +414,24 @@ static int __cg_mount_direct(struct hierarchy *h, const char *controllerpath) if (!controllers) return -ENOMEM; fstype = "cgroup"; + ret = mount("cgroup", controllerpath, fstype, flags, controllers); + } else { + __do_free const char *sb_opts = NULL; + + /* + * Before mounting cgroup2 fs we have to try out best to find + * an existing mount and extract mount existing mount options from it. + * It is important because otherwise we can change cgroup2 superblock + * options. See kernel logic in apply_cgroup_root_flags for more details: + * https://github.com/torvalds/linux/blob/18f7fcd5e69a04df57b563360b88be72471d6b62/kernel/cgroup/cgroup.c#L2047 + * + * If we haven't found an existing mount, just mount a new one with + * an empty list of options. + */ + sb_opts = cgroup2_extract_sb_opts(DEFAULT_CGROUP_MOUNTPOINT); + ret = mount(fstype, controllerpath, fstype, flags, sb_opts); } - ret = mount("cgroup", controllerpath, fstype, flags, controllers); if (ret < 0) return -1; diff --git a/src/cgroups/cgroup_utils.c b/src/cgroups/cgroup_utils.c index 199c985a1..333215f16 100644 --- a/src/cgroups/cgroup_utils.c +++ b/src/cgroups/cgroup_utils.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "../macro.h" #include "../memory_utils.h" @@ -810,3 +811,88 @@ int cgroup_walkup_to_root(int cgroup2_root_fd, int hierarchy_fd, return log_error_errno(-ELOOP, ELOOP, "To many nested cgroups or invalid mount tree. Terminating walk"); } + +/** + * get_mount_opts() - Returns the mount options for a given mnt and with fs_type + * + * @mnt: Mount point. + * @fs_type: File system type. + * + * Returns: mount options if success, NULL if mount is not found or error occurred. + */ +static char *get_mount_opts(const char *mnt, const char *fs_type) { + FILE *fp; + char *mnt_opts = NULL; + struct mntent *ent; + + if (mnt == NULL || fs_type == NULL) + return NULL; + + fp = setmntent("/proc/self/mounts", "r"); + if (!fp) + return NULL; + + while ((ent = getmntent(fp)) != NULL) { + if (strncmp(ent->mnt_dir, mnt, strlen(mnt)) == 0 && + strncmp(ent->mnt_type, fs_type, strlen(fs_type)) == 0) { + mnt_opts = strdup(ent->mnt_opts); /* allocate, caller frees */ + goto out; + } + } + +out: + endmntent(fp); + return mnt_opts; +} + +/** + * cgroup2_extract_sb_opts() - Returns cgroup2 fs superblock options for a given mount point + * + * @mnt: cgroup2 fs mount point. + * + * Returns: sb options if success, NULL if mount is not found or error occurred. + */ +const char *cgroup2_extract_sb_opts(const char *mnt) +{ + __do_free char *mnt_opts = NULL; + char *tok; + char *buf; + size_t buf_len = 0; + static const char *wanted_opts[] = { + "nsdelegate", + "favordynmods", + "memory_localevents", + "memory_recursiveprot", + "memory_hugetlb_accounting", + "pids_localevents", + NULL + }; + + mnt_opts = get_mount_opts(mnt, "cgroup2"); + if (mnt_opts == NULL) { + /* report as info, because it is not critical */ + lxcfs_info("Failed to find an existing cgroup2 mount and get mount options from it"); + return NULL; + } + + for (int i = 0; wanted_opts[i] != NULL; i++) + buf_len += strlen(wanted_opts[i]) + 1; /* for comma or null terminator */ + + buf = calloc(buf_len, 1); + if (!buf) + return NULL; + buf[0] = '\0'; + + lxc_iterate_parts(tok, mnt_opts, ",") { + for (int i = 0; wanted_opts[i] != NULL; i++) { + if (strcmp(tok, wanted_opts[i]) != 0) + continue; + + if (buf[0] != '\0') + (void)strlcat(buf, ",", buf_len); + (void)strlcat(buf, tok, buf_len); + } + } + + return buf; +} diff --git a/src/cgroups/cgroup_utils.h b/src/cgroups/cgroup_utils.h index fde4f9aa2..afd2aea52 100644 --- a/src/cgroups/cgroup_utils.h +++ b/src/cgroups/cgroup_utils.h @@ -98,4 +98,6 @@ static inline bool is_empty_string(const char *s) return !s || strcmp(s, "") == 0; } +const char *cgroup2_extract_sb_opts(const char *mnt); + #endif /* __LXC_CGROUP_UTILS_H */