Files
Abhinav Agarwal 5e3f016bef fusermount: Reject invalid negative mount_max in fuse.conf
mount_max is parsed with %i (signed) from /etc/fuse.conf. The value
-1 is the documented sentinel for "no limit", but any other negative
value (e.g., -2 from a typo) passes the mount_max != -1 guard and
makes the mount_count >= mount_max comparison always true, blocking
all non-root FUSE mounts.

Reject values below -1 with a warning rather than silently accepting
them.

Signed-off-by: Abhinav Agarwal <abhinavagarwal1996@gmail.com>
2026-06-10 11:02:18 +02:00

379 lines
8.8 KiB
C

/*
* FUSE: Filesystem in Userspace
* Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
*
* This program can be distributed under the terms of the GNU GPLv2.
* See the file GPL2.txt.
*/
/* This program parses fuse.conf */
#define _GNU_SOURCE
#include "fuse_config.h"
#include "mount_util.h"
#include "util.h"
#include "fuser_conf.h"
#include <string.h>
#include <stddef.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <mntent.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include "fuse_mount_compat.h"
#if defined HAVE_LISTMOUNT
#include <linux/mount.h>
#include <syscall.h>
#include <stdint.h>
#endif
int user_allow_other;
int mount_max = 1000;
static uid_t oldfsuid;
static gid_t oldfsgid;
// Older versions of musl libc don't unescape entries in /etc/mtab
// unescapes octal sequences like \040 in-place
// That's ok, because unescaping can not extend the length of the string.
void unescape(char *buf)
{
char *src = buf;
char *dest = buf;
while (1) {
char *next_src = strchrnul(src, '\\');
int offset = next_src - src;
memmove(dest, src, offset);
src = next_src;
dest += offset;
if (*src == '\0') {
*dest = *src;
return;
}
src++;
if ('0' <= src[0] && src[0] < '2' &&
'0' <= src[1] && src[1] < '8' &&
'0' <= src[2] && src[2] < '8') {
*dest++ = (src[0] - '0') << 6
| (src[1] - '0') << 3
| (src[2] - '0') << 0;
src += 3;
} else if (src[0] == '\\') {
*dest++ = '\\';
src += 1;
} else {
*dest++ = '\\';
}
}
}
#ifndef IGNORE_MTAB
static int count_fuse_fs_mtab(const char *progname)
{
const struct mntent *entp;
int count = 0;
const char *mtab = _PATH_MOUNTED;
FILE *fp = setmntent(mtab, "r");
if (fp == NULL) {
fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
strerror(errno));
return -1;
}
while ((entp = GETMNTENT(fp)) != NULL) {
if (strcmp(entp->mnt_type, "fuse") == 0 ||
strncmp(entp->mnt_type, "fuse.", 5) == 0)
count++;
}
endmntent(fp);
return count;
}
#ifdef HAVE_LISTMOUNT
static int count_fuse_fs_ls_mnt(const char *progname)
{
#define SMBUF_SIZE 1024
#define MNT_ID_LEN 128
int fuse_count = 0;
uint64_t mnt_ids[MNT_ID_LEN];
unsigned char smbuf[SMBUF_SIZE];
struct mnt_id_req req = {
.size = sizeof(struct mnt_id_req),
};
for (;;) {
int n_mounts;
req.mnt_id = LSMT_ROOT;
n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0);
if (n_mounts == -1) {
if (errno != ENOSYS) {
fprintf(stderr, "%s: failed to list mounts: %s\n", progname,
strerror(errno));
}
return -1;
}
for (int i = 0; i < n_mounts; i++) {
int ret;
req.mnt_id = mnt_ids[i];
req.param = STATMOUNT_FS_TYPE;
ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0);
if (ret) {
if (errno == ENOENT)
continue;
fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname,
req.mnt_id, strerror(errno));
return -1;
}
const struct statmount *sm = (struct statmount *)smbuf;
if (sm->mask & STATMOUNT_FS_TYPE &&
strcmp(&sm->str[sm->fs_type], "fuse") == 0)
fuse_count++;
}
if (n_mounts < MNT_ID_LEN)
break;
req.param = mnt_ids[MNT_ID_LEN - 1];
}
return fuse_count;
}
int count_fuse_fs(const char *progname)
{
int count = count_fuse_fs_ls_mnt(progname);
return count >= 0 ? count : count_fuse_fs_mtab(progname);
}
#else
int count_fuse_fs(const char *progname)
{
return count_fuse_fs_mtab(progname);
}
#endif /* HAVE_LISTMOUNT */
#else
int count_fuse_fs(const char *progname)
{
return 0;
}
#endif /* !IGNORE_MTAB */
static void strip_line(char *line)
{
char *s = strchr(line, '#');
if (s != NULL)
s[0] = '\0';
for (s = line + strlen(line) - 1;
s >= line && isspace((unsigned char) *s); s--) {
}
s[1] = '\0';
for (s = line; isspace((unsigned char) *s); s++)
; /* empty */
if (s != line)
memmove(line, s, strlen(s)+1);
}
static void parse_line(const char *line, int linenum, const char *progname)
{
int tmp;
if (strcmp(line, "user_allow_other") == 0)
user_allow_other = 1;
else if (sscanf(line, "mount_max = %i", &tmp) == 1) {
if (tmp < -1)
fprintf(stderr,
"%s: invalid mount_max = %i in %s at line %i\n",
progname, tmp, FUSE_CONF, linenum);
else
mount_max = tmp;
}
else if (line[0])
fprintf(stderr,
"%s: unknown parameter in %s at line %i: '%s'\n",
progname, FUSE_CONF, linenum, line);
}
void read_conf(const char *progname)
{
FILE *fp = fopen(FUSE_CONF, "r");
if (fp != NULL) {
int linenum = 1;
char line[256];
int isnewline = 1;
while (fgets(line, sizeof(line), fp) != NULL) {
if (isnewline) {
if (line[strlen(line)-1] == '\n') {
strip_line(line);
parse_line(line, linenum, progname);
} else {
isnewline = 0;
}
} else if (line[strlen(line)-1] == '\n') {
fprintf(stderr, "%s: reading %s: line %i too long\n",
progname, FUSE_CONF, linenum);
isnewline = 1;
}
if (isnewline)
linenum++;
}
if (!isnewline) {
fprintf(stderr, "%s: reading %s: missing newline at end of file\n",
progname, FUSE_CONF);
}
if (ferror(fp)) {
fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
exit(1);
}
fclose(fp);
} else if (errno != ENOENT) {
bool fatal = (errno != EACCES && errno != ELOOP &&
errno != ENAMETOOLONG && errno != ENOTDIR &&
errno != EOVERFLOW);
fprintf(stderr, "%s: failed to open %s: %s\n",
progname, FUSE_CONF, strerror(errno));
if (fatal)
exit(1);
}
}
void drop_privs(void)
{
if (getuid() != 0) {
oldfsuid = setfsuid(getuid());
oldfsgid = setfsgid(getgid());
}
}
void restore_privs(void)
{
if (getuid() != 0) {
setfsuid(oldfsuid);
setfsgid(oldfsgid);
}
}
int check_nonroot_mount_count(const char *progname)
{
if (mount_max == -1)
return 0;
int mount_count = count_fuse_fs(progname);
if (mount_count >= mount_max) {
fprintf(stderr,
"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n",
progname, FUSE_CONF);
return -1;
}
return 0;
}
int check_nonroot_dir_access(const char *progname, const char *origmnt,
const char *mnt, const struct stat *stbuf)
{
int res;
if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
fprintf(stderr, "%s: mountpoint %s not owned by user\n",
progname, origmnt);
return -1;
}
res = access(mnt, W_OK);
if (res == -1) {
fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
progname, origmnt);
return -1;
}
return 0;
}
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
int check_nonroot_fstype(const char *progname, const struct statfs *fs_buf)
{
size_t i;
/* Do not permit mounting over anything in procfs - it has a couple
* places to which we have "write access" without being supposed to be
* able to just put anything we want there.
* Luckily, without allow_other, we can't get other users to actually
* use any fake information we try to put there anyway.
* Use a whitelist to be safe.
*/
/* Define permitted filesystems for the mount target. This was
* originally the same list as used by the ecryptfs mount helper
* (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
* but got expanded as we found more filesystems that needed to be
* overlaid.
*/
typeof(fs_buf->f_type) f_type_whitelist[] = {
0x61756673 /* AUFS_SUPER_MAGIC */,
0x00000187 /* AUTOFS_SUPER_MAGIC */,
0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
0x9123683E /* BTRFS_SUPER_MAGIC */,
0x00C36400 /* CEPH_SUPER_MAGIC */,
0xFF534D42 /* CIFS_MAGIC_NUMBER */,
0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
0xF2F52010 /* F2FS_SUPER_MAGIC */,
0x65735546 /* FUSE_SUPER_MAGIC */,
0x01161970 /* GFS2_MAGIC */,
0x47504653 /* GPFS_SUPER_MAGIC */,
0x0000482b /* HFSPLUS_SUPER_MAGIC */,
0x000072B6 /* JFFS2_SUPER_MAGIC */,
0x3153464A /* JFS_SUPER_MAGIC */,
0x0BD00BD0 /* LL_SUPER_MAGIC */,
0X00004D44 /* MSDOS_SUPER_MAGIC */,
0x0000564C /* NCP_SUPER_MAGIC */,
0x00006969 /* NFS_SUPER_MAGIC */,
0x00003434 /* NILFS_SUPER_MAGIC */,
0x5346544E /* NTFS_SB_MAGIC */,
0x7366746E /* NTFS3_SUPER_MAGIC */,
0x5346414f /* OPENAFS_SUPER_MAGIC */,
0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
0x52654973 /* REISERFS_SUPER_MAGIC */,
0xFE534D42 /* SMB2_SUPER_MAGIC */,
0x73717368 /* SQUASHFS_MAGIC */,
0x01021994 /* TMPFS_MAGIC */,
0x24051905 /* UBIFS_SUPER_MAGIC */,
0x18031977 /* WEKAFS_SUPER_MAGIC */,
#if __SIZEOF_LONG__ > 4
0x736675005346544e /* UFSD */,
#endif
0x58465342 /* XFS_SB_MAGIC */,
0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
0x858458f6 /* RAMFS_MAGIC */,
};
for (i = 0; i < ARRAY_SIZE(f_type_whitelist); i++) {
if (f_type_whitelist[i] == fs_buf->f_type)
return 0;
}
fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
progname, (unsigned long)fs_buf->f_type);
return -1;
}