xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit ebac13bfc52faac7d714b4166851ff63a43d563f

Fix unsafe IPC pipe reuse (CVE-2026-110237)
Check that the opened pipe has expected type, owner, group and
permissions.

Thanks to Andrew Savchenko and Salvatore Bonaccorso (a.k.a. carnil).

See https://www.cve.org/CVERecord?id=CVE-2026-110237.
Fixes https://github.com/vifm/vifm/issues/1172.
Author: xaizek
Author date (UTC): 2026-10-17 14:22
Committer name: xaizek
Committer date (UTC): 2026-10-17 14:50
Parent(s): e63066e0b8c0456e6cdda3565e68eb2899460648
Signing key: 99DC5E4DB05F6BE2
Tree: eed9d19eab0c6026e67996a9358adca0e6b38936
File Lines added Lines deleted
ChangeLog 3 0
THANKS 1 0
src/ipc.c 17 4
tests/misc/ipc.c 66 1
tests/test-support/test-utils.c 12 1
tests/test-support/test-utils.h 4 0
File ChangeLog changed (mode: 100644) (index 4f0c7bd29..a37b3ae2e)
325 325 Fixed graphics from miller preview staying visible after switching Fixed graphics from miller preview staying visible after switching
326 326 'millerview' off. 'millerview' off.
327 327
328 Fixed unsafe IPC pipe reuse (CVE-2026-110237). Thanks to Andrew Savchenko
329 and Salvatore Bonaccorso (a.k.a. carnil).
330
328 331 0.14-beta to 0.14 (2025-02-08) 0.14-beta to 0.14 (2025-02-08)
329 332
330 333 Improved documentation on zh/zl menu keys a bit. Improved documentation on zh/zl menu keys a bit.
File THANKS changed (mode: 100644) (index f5c82ee11..06a667c1c)
... ... Ruslan Osmanov (rosmanov)
294 294 Russell Urquhart Russell Urquhart
295 295 rwtallant13 rwtallant13
296 296 Safal Piya (mrsafalpiya) Safal Piya (mrsafalpiya)
297 Salvatore Bonaccorso (carnil)
297 298 SanLe SanLe
298 299 santhoshr santhoshr
299 300 Sassan Haradji (sassanh) Sassan Haradji (sassanh)
File src/ipc.c changed (mode: 100644) (index 5d65686c5..f10251fee)
40 40 # endif # endif
41 41 #endif #endif
42 42
43 #include <sys/stat.h> /* mkfifo() stat() */
43 #include <sys/stat.h> /* fstat() mkfifo() stat() */
44 44 #include <dirent.h> /* DIR closedir() opendir() readdir() */ #include <dirent.h> /* DIR closedir() opendir() readdir() */
45 45 #include <fcntl.h> #include <fcntl.h>
46 #include <unistd.h> /* close() open() select() unlink() usleep() */
46 #include <unistd.h> /* close() getgid() getuid() open() select() unlink()
47 usleep() */
47 48
48 49 #include <errno.h> /* EACCES EEXIST EDQUOT ENOSPC ENXIO errno */ #include <errno.h> /* EACCES EEXIST EDQUOT ENOSPC ENXIO errno */
49 50 #include <stddef.h> /* NULL size_t ssize_t */ #include <stddef.h> /* NULL size_t ssize_t */
 
... ... static read_pipe_t
394 395 try_use_pipe(const char path[], int *fatal) try_use_pipe(const char path[], int *fatal)
395 396 { {
396 397 #ifndef WIN32_PIPE_READ #ifndef WIN32_PIPE_READ
398 enum { IPC_PIPE_PERMS = 0600 };
399
397 400 FILE *f; FILE *f;
398 401 int fd; int fd;
399 402
400 403 *fatal = 0; *fatal = 0;
401 404
402 405 /* Try to create a pipe. */ /* Try to create a pipe. */
403 if(mkfifo(path, 0600) == 0)
406 if(mkfifo(path, IPC_PIPE_PERMS) == 0)
404 407 { {
405 408 /* Open if created. */ /* Open if created. */
406 409 const int fd = open(path, O_RDONLY | O_NONBLOCK); const int fd = open(path, O_RDONLY | O_NONBLOCK);
 
... ... try_use_pipe(const char path[], int *fatal)
429 432
430 433 /* Use this file if we're the only one willing to read from it. */ /* Use this file if we're the only one willing to read from it. */
431 434 fd = open(path, O_RDONLY | O_NONBLOCK); fd = open(path, O_RDONLY | O_NONBLOCK);
432
433 435 if(fd == -1) if(fd == -1)
434 436 { {
435 437 return NULL_READ_PIPE; return NULL_READ_PIPE;
436 438 } }
437 439
440 struct stat statbuf;
441 if(fstat(fd, &statbuf) != 0 || statbuf.st_uid != getuid() ||
442 statbuf.st_gid != getgid() || !S_ISFIFO(statbuf.st_mode) ||
443 (statbuf.st_mode & 07777) != IPC_PIPE_PERMS)
444 {
445 (void)close(fd);
446 /* Either can't query file's metadata or its type, ownership or permissions
447 * are incorrect. Using such a pipe may be unsafe, so skip it. */
448 return NULL_READ_PIPE;
449 }
450
438 451 f = fdopen(fd, "r"); f = fdopen(fd, "r");
439 452 if(f == NULL_READ_PIPE) if(f == NULL_READ_PIPE)
440 453 { {
File tests/misc/ipc.c changed (mode: 100644) (index 0211cac32..42b3aa618)
1 1 #include <stic.h> #include <stic.h>
2 2
3 #include <unistd.h> /* chown() getgid() getuid() */
4
3 5 #include <stddef.h> /* NULL */ #include <stddef.h> /* NULL */
4 6 #include <stdlib.h> /* free() malloc() */ #include <stdlib.h> /* free() malloc() */
5 7 #include <string.h> /* memset() strcmp() strdup() */ #include <string.h> /* memset() strcmp() strdup() */
6 8
7 9 #include <test-utils.h> #include <test-utils.h>
8 10
11 #include "../../src/compat/fs_limits.h"
12 #include "../../src/utils/env.h"
9 13 #include "../../src/utils/str.h" #include "../../src/utils/str.h"
10 14 #include "../../src/utils/string_array.h" #include "../../src/utils/string_array.h"
11 15 #include "../../src/background.h" #include "../../src/background.h"
 
... ... static void other_instance(bg_op_t *bg_op, void *arg);
20 24 static int enabled_and_not_in_wine(void); static int enabled_and_not_in_wine(void);
21 25 static int enabled_and_not_windows(void); static int enabled_and_not_windows(void);
22 26
23 static const char NAME[] = "vifm-test";
27 #define NAME "vifm-test"
28
24 29 static int nmessages; static int nmessages;
25 30 static char *message; static char *message;
26 31 static int nmessages2; static int nmessages2;
 
... ... TEST(no_eval_to_self, IF(enabled_and_not_in_wine))
251 256 assert_string_equal(NULL, message); assert_string_equal(NULL, message);
252 257 } }
253 258
259 TEST(pipe_is_reused_safely, IF(enabled_and_not_windows))
260 {
261 char ipc_dir[PATH_MAX + 1];
262 make_abs_path(ipc_dir, sizeof(ipc_dir), SANDBOX_PATH, "", /*cwd=*/NULL);
263
264 /* $TMPDIR because of how get_tmpdir() is implemented. */
265 env_set("TMPDIR", ipc_dir);
266
267 ipc_t *ipc;
268
269 /* There are no other pipes. */
270 ipc = ipc_init(NAME, &test_ipc_args, &test_ipc_eval);
271 assert_string_equal(NAME, ipc_get_name(ipc));
272 ipc_free(ipc);
273
274 /* The default pipe name is in use by a regular file. */
275 create_file(SANDBOX_PATH "/vifm-ipc-" NAME);
276 ipc = ipc_init(NAME, &test_ipc_args, &test_ipc_eval);
277 assert_string_equal(NAME "1", ipc_get_name(ipc));
278 ipc_free(ipc);
279 remove_file(SANDBOX_PATH "/vifm-ipc-" NAME);
280
281 /* The default pipe name is in use by a pipe with bad permissions. */
282 assert_success(make_fifo(SANDBOX_PATH "/vifm-ipc-" NAME, 0660));
283 ipc = ipc_init(NAME, &test_ipc_args, &test_ipc_eval);
284 assert_string_equal(NAME "1", ipc_get_name(ipc));
285 ipc_free(ipc);
286 remove_file(SANDBOX_PATH "/vifm-ipc-" NAME);
287
288 #ifndef _WIN32
289 if(!regular_unix_user())
290 {
291 /* The default pipe name is in use by a pipe with a bad owner. */
292 assert_success(make_fifo(SANDBOX_PATH "/vifm-ipc-" NAME, 0600));
293 assert_success(chown(SANDBOX_PATH "/vifm-ipc-" NAME, 1234, getgid()));
294 ipc = ipc_init(NAME, &test_ipc_args, &test_ipc_eval);
295 assert_string_equal(NAME "1", ipc_get_name(ipc));
296 ipc_free(ipc);
297 remove_file(SANDBOX_PATH "/vifm-ipc-" NAME);
298
299 /* The default pipe name is in use by a pipe with a bad group. */
300 assert_success(make_fifo(SANDBOX_PATH "/vifm-ipc-" NAME, 0600));
301 assert_success(chown(SANDBOX_PATH "/vifm-ipc-" NAME, getuid(), 1234));
302 ipc = ipc_init(NAME, &test_ipc_args, &test_ipc_eval);
303 assert_string_equal(NAME "1", ipc_get_name(ipc));
304 ipc_free(ipc);
305 remove_file(SANDBOX_PATH "/vifm-ipc-" NAME);
306 }
307 #endif
308
309 /* The default pipe name is in use by an abandoned pipe. */
310 assert_success(make_fifo(SANDBOX_PATH "/vifm-ipc-" NAME, 0600));
311 ipc = ipc_init(NAME, &test_ipc_args, &test_ipc_eval);
312 assert_string_equal(NAME, ipc_get_name(ipc));
313 ipc_free(ipc);
314 no_remove_file(SANDBOX_PATH "/vifm-ipc-" NAME);
315
316 env_remove("TMPDIR");
317 }
318
254 319 static void static void
255 320 test_ipc_args(char *args[]) test_ipc_args(char *args[])
256 321 { {
File tests/test-support/test-utils.c changed (mode: 100644) (index 76a047539..0da20115e)
2 2
3 3 #include <stic.h> #include <stic.h>
4 4
5 #include <sys/stat.h> /* chmod() */
5 #include <sys/stat.h> /* chmod() mkfifo() */
6 6 #include <sys/time.h> /* timeval utimes() */ #include <sys/time.h> /* timeval utimes() */
7 7 #include <unistd.h> /* access() geteuid() rmdir() symlink() usleep() */ #include <unistd.h> /* access() geteuid() rmdir() symlink() usleep() */
8 8
 
... ... make_symlink(const char target[], const char linkpath[])
471 471 #endif #endif
472 472 } }
473 473
474 int
475 make_fifo(const char path[], int mode)
476 {
477 #ifndef _WIN32
478 return mkfifo(path, mode);
479 #else
480 assert_fail("No creation of named pipes on Windows' file-systems.");
481 return 1;
482 #endif
483 }
484
474 485 void void
475 486 remove_dir(const char path[]) remove_dir(const char path[])
476 487 { {
File tests/test-support/test-utils.h changed (mode: 100644) (index f6aa2c919..4f97fcdb6)
... ... void make_file(const char path[], const char contents[]);
84 84 * returned. */ * returned. */
85 85 int make_symlink(const char target[], const char linkpath[]); int make_symlink(const char target[], const char linkpath[]);
86 86
87 /* Makes a named pipe. Returns zero on success, otherwise non-zero is
88 * returned. */
89 int make_fifo(const char path[], int mode);
90
87 91 /* Removes empty directory at specified path. */ /* Removes empty directory at specified path. */
88 92 void remove_dir(const char path[]); void remove_dir(const char path[]);
89 93
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master