Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tools api fs: More thread safety for global filesystem variables

Multiple threads, such as with "perf top", may race to initialize a
file system path like hugetlbfs. The racy initialization of the path
leads to at least memory leaks. To avoid this initialize each fs for
reading the mount point path with pthread_once.

Mounting the file system may also be racy, so introduce a mutex over
the function. This does mean that the path is being accessed with and
without a mutex, which is inherently racy but hopefully benign,
especially as there are fewer callers to fs__mount.

Remove the fs__entries by directly using global variables, this was
done as no argument like the index can be passed to the init once
routine.

Issue found and tested with "perf top" and address sanitizer.

Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: bpf@vger.kernel.org
Link: https://lore.kernel.org/r/20230609224004.180988-1-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Ian Rogers and committed by
Arnaldo Carvalho de Melo
97d5f2e9 8dc26b6f

+86 -125
+86 -125
tools/lib/api/fs/fs.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0 2 + #include <assert.h> 2 3 #include <ctype.h> 3 4 #include <errno.h> 4 5 #include <limits.h> ··· 11 10 #include <sys/types.h> 12 11 #include <sys/stat.h> 13 12 #include <fcntl.h> 13 + #include <pthread.h> 14 14 #include <unistd.h> 15 15 #include <sys/mount.h> 16 16 ··· 45 43 #define BPF_FS_MAGIC 0xcafe4a11 46 44 #endif 47 45 48 - static const char * const sysfs__fs_known_mountpoints[] = { 46 + static const char * const sysfs__known_mountpoints[] = { 49 47 "/sys", 50 48 0, 51 49 }; ··· 88 86 }; 89 87 90 88 struct fs { 91 - const char *name; 92 - const char * const *mounts; 89 + const char * const name; 90 + const char * const * const mounts; 93 91 char *path; 94 - bool found; 95 - bool checked; 96 - long magic; 97 - }; 98 - 99 - enum { 100 - FS__SYSFS = 0, 101 - FS__PROCFS = 1, 102 - FS__DEBUGFS = 2, 103 - FS__TRACEFS = 3, 104 - FS__HUGETLBFS = 4, 105 - FS__BPF_FS = 5, 92 + pthread_mutex_t mount_mutex; 93 + const long magic; 106 94 }; 107 95 108 96 #ifndef TRACEFS_MAGIC 109 97 #define TRACEFS_MAGIC 0x74726163 110 98 #endif 111 99 112 - static struct fs fs__entries[] = { 113 - [FS__SYSFS] = { 114 - .name = "sysfs", 115 - .mounts = sysfs__fs_known_mountpoints, 116 - .magic = SYSFS_MAGIC, 117 - .checked = false, 118 - }, 119 - [FS__PROCFS] = { 120 - .name = "proc", 121 - .mounts = procfs__known_mountpoints, 122 - .magic = PROC_SUPER_MAGIC, 123 - .checked = false, 124 - }, 125 - [FS__DEBUGFS] = { 126 - .name = "debugfs", 127 - .mounts = debugfs__known_mountpoints, 128 - .magic = DEBUGFS_MAGIC, 129 - .checked = false, 130 - }, 131 - [FS__TRACEFS] = { 132 - .name = "tracefs", 133 - .mounts = tracefs__known_mountpoints, 134 - .magic = TRACEFS_MAGIC, 135 - .checked = false, 136 - }, 137 - [FS__HUGETLBFS] = { 138 - .name = "hugetlbfs", 139 - .mounts = hugetlbfs__known_mountpoints, 140 - .magic = HUGETLBFS_MAGIC, 141 - .checked = false, 142 - }, 143 - [FS__BPF_FS] = { 144 - .name = "bpf", 145 - .mounts = bpf_fs__known_mountpoints, 146 - .magic = BPF_FS_MAGIC, 147 - .checked = false, 148 - }, 149 - }; 100 + static void fs__init_once(struct fs *fs); 101 + static const char *fs__mountpoint(const struct fs *fs); 102 + static const char *fs__mount(struct fs *fs); 103 + 104 + #define FS(lower_name, fs_name, upper_name) \ 105 + static struct fs fs__##lower_name = { \ 106 + .name = #fs_name, \ 107 + .mounts = lower_name##__known_mountpoints, \ 108 + .magic = upper_name##_MAGIC, \ 109 + .mount_mutex = PTHREAD_MUTEX_INITIALIZER, \ 110 + }; \ 111 + \ 112 + static void lower_name##_init_once(void) \ 113 + { \ 114 + struct fs *fs = &fs__##lower_name; \ 115 + \ 116 + fs__init_once(fs); \ 117 + } \ 118 + \ 119 + const char *lower_name##__mountpoint(void) \ 120 + { \ 121 + static pthread_once_t init_once = PTHREAD_ONCE_INIT; \ 122 + struct fs *fs = &fs__##lower_name; \ 123 + \ 124 + pthread_once(&init_once, lower_name##_init_once); \ 125 + return fs__mountpoint(fs); \ 126 + } \ 127 + \ 128 + const char *lower_name##__mount(void) \ 129 + { \ 130 + const char *mountpoint = lower_name##__mountpoint(); \ 131 + struct fs *fs = &fs__##lower_name; \ 132 + \ 133 + if (mountpoint) \ 134 + return mountpoint; \ 135 + \ 136 + return fs__mount(fs); \ 137 + } \ 138 + \ 139 + bool lower_name##__configured(void) \ 140 + { \ 141 + return lower_name##__mountpoint() != NULL; \ 142 + } 143 + 144 + FS(sysfs, sysfs, SYSFS); 145 + FS(procfs, procfs, PROC_SUPER); 146 + FS(debugfs, debugfs, DEBUGFS); 147 + FS(tracefs, tracefs, TRACEFS); 148 + FS(hugetlbfs, hugetlbfs, HUGETLBFS); 149 + FS(bpf_fs, bpf, BPF_FS); 150 150 151 151 static bool fs__read_mounts(struct fs *fs) 152 152 { 153 - bool found = false; 154 153 char type[100]; 155 154 FILE *fp; 156 155 char path[PATH_MAX + 1]; ··· 160 157 if (fp == NULL) 161 158 return false; 162 159 163 - while (!found && 164 - fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n", 160 + while (fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n", 165 161 path, type) == 2) { 166 162 167 163 if (strcmp(type, fs->name) == 0) { 168 - free(fs->path); 169 164 fs->path = strdup(path); 170 - if (!fs->path) 171 - return false; 172 - found = true; 165 + fclose(fp); 166 + return fs->path != NULL; 173 167 } 174 168 } 175 - 176 169 fclose(fp); 177 - fs->checked = true; 178 - return fs->found = found; 170 + return false; 179 171 } 180 172 181 173 static int fs__valid_mount(const char *fs, long magic) ··· 192 194 ptr = fs->mounts; 193 195 while (*ptr) { 194 196 if (fs__valid_mount(*ptr, fs->magic) == 0) { 195 - free(fs->path); 196 197 fs->path = strdup(*ptr); 197 198 if (!fs->path) 198 199 return false; 199 - fs->found = true; 200 200 return true; 201 201 } 202 202 ptr++; ··· 232 236 if (!override_path) 233 237 return false; 234 238 235 - free(fs->path); 236 239 fs->path = strdup(override_path); 237 240 if (!fs->path) 238 241 return false; 239 - fs->found = true; 240 - fs->checked = true; 241 242 return true; 242 243 } 243 244 244 - static const char *fs__get_mountpoint(struct fs *fs) 245 + static void fs__init_once(struct fs *fs) 245 246 { 246 - if (fs__env_override(fs)) 247 - return fs->path; 248 - 249 - if (fs__check_mounts(fs)) 250 - return fs->path; 251 - 252 - if (fs__read_mounts(fs)) 253 - return fs->path; 254 - 255 - return NULL; 247 + if (!fs__env_override(fs) && 248 + !fs__check_mounts(fs) && 249 + !fs__read_mounts(fs)) { 250 + assert(!fs->path); 251 + } else { 252 + assert(fs->path); 253 + } 256 254 } 257 255 258 - static const char *fs__mountpoint(int idx) 256 + static const char *fs__mountpoint(const struct fs *fs) 259 257 { 260 - struct fs *fs = &fs__entries[idx]; 261 - 262 - if (fs->found) 263 - return (const char *)fs->path; 264 - 265 - /* the mount point was already checked for the mount point 266 - * but and did not exist, so return NULL to avoid scanning again. 267 - * This makes the found and not found paths cost equivalent 268 - * in case of multiple calls. 269 - */ 270 - if (fs->checked) 271 - return NULL; 272 - 273 - return fs__get_mountpoint(fs); 258 + return fs->path; 274 259 } 275 260 276 261 static const char *mount_overload(struct fs *fs) ··· 266 289 return getenv(upper_name) ?: *fs->mounts; 267 290 } 268 291 269 - static const char *fs__mount(int idx) 292 + static const char *fs__mount(struct fs *fs) 270 293 { 271 - struct fs *fs = &fs__entries[idx]; 272 294 const char *mountpoint; 273 295 274 - if (fs__mountpoint(idx)) 275 - return (const char *)fs->path; 296 + pthread_mutex_lock(&fs->mount_mutex); 297 + 298 + /* Check if path found inside the mutex to avoid races with other callers of mount. */ 299 + mountpoint = fs__mountpoint(fs); 300 + if (mountpoint) 301 + goto out; 276 302 277 303 mountpoint = mount_overload(fs); 278 304 279 - if (mount(NULL, mountpoint, fs->name, 0, NULL) < 0) 280 - return NULL; 281 - 282 - return fs__check_mounts(fs) ? fs->path : NULL; 305 + if (mount(NULL, mountpoint, fs->name, 0, NULL) == 0 && 306 + fs__valid_mount(mountpoint, fs->magic) == 0) { 307 + fs->path = strdup(mountpoint); 308 + mountpoint = fs->path; 309 + } 310 + out: 311 + pthread_mutex_unlock(&fs->mount_mutex); 312 + return mountpoint; 283 313 } 284 - 285 - #define FS(name, idx) \ 286 - const char *name##__mountpoint(void) \ 287 - { \ 288 - return fs__mountpoint(idx); \ 289 - } \ 290 - \ 291 - const char *name##__mount(void) \ 292 - { \ 293 - return fs__mount(idx); \ 294 - } \ 295 - \ 296 - bool name##__configured(void) \ 297 - { \ 298 - return name##__mountpoint() != NULL; \ 299 - } 300 - 301 - FS(sysfs, FS__SYSFS); 302 - FS(procfs, FS__PROCFS); 303 - FS(debugfs, FS__DEBUGFS); 304 - FS(tracefs, FS__TRACEFS); 305 - FS(hugetlbfs, FS__HUGETLBFS); 306 - FS(bpf_fs, FS__BPF_FS); 307 314 308 315 int filename__read_int(const char *filename, int *value) 309 316 {