Skip to content

Commit

Permalink
fs: create and use seq_show_option for escaping
Browse files Browse the repository at this point in the history
Many file systems that implement the show_options hook fail to correctly
escape their output which could lead to unescaped characters (e.g.  new
lines) leaking into /proc/mounts and /proc/[pid]/mountinfo files.  This
could lead to confusion, spoofed entries (resulting in things like
systemd issuing false d-bus "mount" notifications), and who knows what
else.  This looks like it would only be the root user stepping on
themselves, but it's possible weird things could happen in containers or
in other situations with delegated mount privileges.

Here's an example using overlay with setuid fusermount trusting the
contents of /proc/mounts (via the /etc/mtab symlink).  Imagine the use
of "sudo" is something more sneaky:

  $ BASE="ovl"
  $ MNT="$BASE/mnt"
  $ LOW="$BASE/lower"
  $ UP="$BASE/upper"
  $ WORK="$BASE/work/ 0 0
  none /proc fuse.pwn user_id=1000"
  $ mkdir -p "$LOW" "$UP" "$WORK"
  $ sudo mount -t overlay -o "lowerdir=$LOW,upperdir=$UP,workdir=$WORK" none /mnt
  $ cat /proc/mounts
  none /root/ovl/mnt overlay rw,relatime,lowerdir=ovl/lower,upperdir=ovl/upper,workdir=ovl/work/ 0 0
  none /proc fuse.pwn user_id=1000 0 0
  $ fusermount -u /proc
  $ cat /proc/mounts
  cat: /proc/mounts: No such file or directory

This fixes the problem by adding new seq_show_option and
seq_show_option_n helpers, and updating the vulnerable show_option
handlers to use them as needed.  Some, like SELinux, need to be open
coded due to unusual existing escape mechanisms.

[[email protected]: add lost chunk, per Kees]
[[email protected]: seq_show_option should be using const parameters]
Signed-off-by: Kees Cook <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
Acked-by: Jan Kara <[email protected]>
Acked-by: Paul Moore <[email protected]>
Cc: J. R. Okajima <[email protected]>
Signed-off-by: Kees Cook <[email protected]>
Cc: <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
kees authored and torvalds committed Sep 4, 2015
1 parent 4635929 commit a068acf
Show file tree
Hide file tree
Showing 15 changed files with 71 additions and 30 deletions.
2 changes: 1 addition & 1 deletion fs/ceph/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root)
if (fsopt->max_readdir_bytes != CEPH_MAX_READDIR_BYTES_DEFAULT)
seq_printf(m, ",readdir_max_bytes=%d", fsopt->max_readdir_bytes);
if (strcmp(fsopt->snapdir_name, CEPH_SNAPDIRNAME_DEFAULT))
seq_printf(m, ",snapdirname=%s", fsopt->snapdir_name);
seq_show_option(m, "snapdirname", fsopt->snapdir_name);

return 0;
}
Expand Down
6 changes: 3 additions & 3 deletions fs/cifs/cifsfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,17 +394,17 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
struct sockaddr *srcaddr;
srcaddr = (struct sockaddr *)&tcon->ses->server->srcaddr;

seq_printf(s, ",vers=%s", tcon->ses->server->vals->version_string);
seq_show_option(s, "vers", tcon->ses->server->vals->version_string);
cifs_show_security(s, tcon->ses);
cifs_show_cache_flavor(s, cifs_sb);

if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)
seq_puts(s, ",multiuser");
else if (tcon->ses->user_name)
seq_printf(s, ",username=%s", tcon->ses->user_name);
seq_show_option(s, "username", tcon->ses->user_name);

if (tcon->ses->domainName)
seq_printf(s, ",domain=%s", tcon->ses->domainName);
seq_show_option(s, "domain", tcon->ses->domainName);

if (srcaddr->sa_family != AF_UNSPEC) {
struct sockaddr_in *saddr4;
Expand Down
4 changes: 2 additions & 2 deletions fs/ext4/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1776,10 +1776,10 @@ static inline void ext4_show_quota_options(struct seq_file *seq,
}

if (sbi->s_qf_names[USRQUOTA])
seq_printf(seq, ",usrjquota=%s", sbi->s_qf_names[USRQUOTA]);
seq_show_option(seq, "usrjquota", sbi->s_qf_names[USRQUOTA]);

if (sbi->s_qf_names[GRPQUOTA])
seq_printf(seq, ",grpjquota=%s", sbi->s_qf_names[GRPQUOTA]);
seq_show_option(seq, "grpjquota", sbi->s_qf_names[GRPQUOTA]);
#endif
}

Expand Down
6 changes: 3 additions & 3 deletions fs/gfs2/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1334,11 +1334,11 @@ static int gfs2_show_options(struct seq_file *s, struct dentry *root)
if (is_ancestor(root, sdp->sd_master_dir))
seq_puts(s, ",meta");
if (args->ar_lockproto[0])
seq_printf(s, ",lockproto=%s", args->ar_lockproto);
seq_show_option(s, "lockproto", args->ar_lockproto);
if (args->ar_locktable[0])
seq_printf(s, ",locktable=%s", args->ar_locktable);
seq_show_option(s, "locktable", args->ar_locktable);
if (args->ar_hostdata[0])
seq_printf(s, ",hostdata=%s", args->ar_hostdata);
seq_show_option(s, "hostdata", args->ar_hostdata);
if (args->ar_spectator)
seq_puts(s, ",spectator");
if (args->ar_localflocks)
Expand Down
4 changes: 2 additions & 2 deletions fs/hfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ static int hfs_show_options(struct seq_file *seq, struct dentry *root)
struct hfs_sb_info *sbi = HFS_SB(root->d_sb);

if (sbi->s_creator != cpu_to_be32(0x3f3f3f3f))
seq_printf(seq, ",creator=%.4s", (char *)&sbi->s_creator);
seq_show_option_n(seq, "creator", (char *)&sbi->s_creator, 4);
if (sbi->s_type != cpu_to_be32(0x3f3f3f3f))
seq_printf(seq, ",type=%.4s", (char *)&sbi->s_type);
seq_show_option_n(seq, "type", (char *)&sbi->s_type, 4);
seq_printf(seq, ",uid=%u,gid=%u",
from_kuid_munged(&init_user_ns, sbi->s_uid),
from_kgid_munged(&init_user_ns, sbi->s_gid));
Expand Down
4 changes: 2 additions & 2 deletions fs/hfsplus/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ int hfsplus_show_options(struct seq_file *seq, struct dentry *root)
struct hfsplus_sb_info *sbi = HFSPLUS_SB(root->d_sb);

if (sbi->creator != HFSPLUS_DEF_CR_TYPE)
seq_printf(seq, ",creator=%.4s", (char *)&sbi->creator);
seq_show_option_n(seq, "creator", (char *)&sbi->creator, 4);
if (sbi->type != HFSPLUS_DEF_CR_TYPE)
seq_printf(seq, ",type=%.4s", (char *)&sbi->type);
seq_show_option_n(seq, "type", (char *)&sbi->type, 4);
seq_printf(seq, ",umask=%o,uid=%u,gid=%u", sbi->umask,
from_kuid_munged(&init_user_ns, sbi->uid),
from_kgid_munged(&init_user_ns, sbi->gid));
Expand Down
2 changes: 1 addition & 1 deletion fs/hostfs/hostfs_kern.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ static int hostfs_show_options(struct seq_file *seq, struct dentry *root)
size_t offset = strlen(root_ino) + 1;

if (strlen(root_path) > offset)
seq_printf(seq, ",%s", root_path + offset);
seq_show_option(seq, root_path + offset, NULL);

if (append)
seq_puts(seq, ",append");
Expand Down
4 changes: 2 additions & 2 deletions fs/ocfs2/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1563,8 +1563,8 @@ static int ocfs2_show_options(struct seq_file *s, struct dentry *root)
seq_printf(s, ",localflocks,");

if (osb->osb_cluster_stack[0])
seq_printf(s, ",cluster_stack=%.*s", OCFS2_STACK_LABEL_LEN,
osb->osb_cluster_stack);
seq_show_option_n(s, "cluster_stack", osb->osb_cluster_stack,
OCFS2_STACK_LABEL_LEN);
if (opts & OCFS2_MOUNT_USRQUOTA)
seq_printf(s, ",usrquota");
if (opts & OCFS2_MOUNT_GRPQUOTA)
Expand Down
6 changes: 3 additions & 3 deletions fs/overlayfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,10 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
struct super_block *sb = dentry->d_sb;
struct ovl_fs *ufs = sb->s_fs_info;

seq_printf(m, ",lowerdir=%s", ufs->config.lowerdir);
seq_show_option(m, "lowerdir", ufs->config.lowerdir);
if (ufs->config.upperdir) {
seq_printf(m, ",upperdir=%s", ufs->config.upperdir);
seq_printf(m, ",workdir=%s", ufs->config.workdir);
seq_show_option(m, "upperdir", ufs->config.upperdir);
seq_show_option(m, "workdir", ufs->config.workdir);
}
return 0;
}
Expand Down
8 changes: 5 additions & 3 deletions fs/reiserfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -714,18 +714,20 @@ static int reiserfs_show_options(struct seq_file *seq, struct dentry *root)
seq_puts(seq, ",acl");

if (REISERFS_SB(s)->s_jdev)
seq_printf(seq, ",jdev=%s", REISERFS_SB(s)->s_jdev);
seq_show_option(seq, "jdev", REISERFS_SB(s)->s_jdev);

if (journal->j_max_commit_age != journal->j_default_max_commit_age)
seq_printf(seq, ",commit=%d", journal->j_max_commit_age);

#ifdef CONFIG_QUOTA
if (REISERFS_SB(s)->s_qf_names[USRQUOTA])
seq_printf(seq, ",usrjquota=%s", REISERFS_SB(s)->s_qf_names[USRQUOTA]);
seq_show_option(seq, "usrjquota",
REISERFS_SB(s)->s_qf_names[USRQUOTA]);
else if (opts & (1 << REISERFS_USRQUOTA))
seq_puts(seq, ",usrquota");
if (REISERFS_SB(s)->s_qf_names[GRPQUOTA])
seq_printf(seq, ",grpjquota=%s", REISERFS_SB(s)->s_qf_names[GRPQUOTA]);
seq_show_option(seq, "grpjquota",
REISERFS_SB(s)->s_qf_names[GRPQUOTA]);
else if (opts & (1 << REISERFS_GRPQUOTA))
seq_puts(seq, ",grpquota");
if (REISERFS_SB(s)->s_jquota_fmt) {
Expand Down
4 changes: 2 additions & 2 deletions fs/xfs/xfs_super.c
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,9 @@ xfs_showargs(
seq_printf(m, "," MNTOPT_LOGBSIZE "=%dk", mp->m_logbsize >> 10);

if (mp->m_logname)
seq_printf(m, "," MNTOPT_LOGDEV "=%s", mp->m_logname);
seq_show_option(m, MNTOPT_LOGDEV, mp->m_logname);
if (mp->m_rtname)
seq_printf(m, "," MNTOPT_RTDEV "=%s", mp->m_rtname);
seq_show_option(m, MNTOPT_RTDEV, mp->m_rtname);

if (mp->m_dalign > 0)
seq_printf(m, "," MNTOPT_SUNIT "=%d",
Expand Down
35 changes: 35 additions & 0 deletions include/linux/seq_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,41 @@ static inline struct user_namespace *seq_user_ns(struct seq_file *seq)
#endif
}

/**
* seq_show_options - display mount options with appropriate escapes.
* @m: the seq_file handle
* @name: the mount option name
* @value: the mount option name's value, can be NULL
*/
static inline void seq_show_option(struct seq_file *m, const char *name,
const char *value)
{
seq_putc(m, ',');
seq_escape(m, name, ",= \t\n\\");
if (value) {
seq_putc(m, '=');
seq_escape(m, value, ", \t\n\\");
}
}

/**
* seq_show_option_n - display mount options with appropriate escapes
* where @value must be a specific length.
* @m: the seq_file handle
* @name: the mount option name
* @value: the mount option name's value, cannot be NULL
* @length: the length of @value to display
*
* This is a macro since this uses "length" to define the size of the
* stack buffer.
*/
#define seq_show_option_n(m, name, value, length) { \
char val_buf[length + 1]; \
strncpy(val_buf, value, length); \
val_buf[length] = '\0'; \
seq_show_option(m, name, val_buf); \
}

#define SEQ_START_TOKEN ((void *)1)
/*
* Helpers for iteration over list_head-s in seq_files
Expand Down
7 changes: 4 additions & 3 deletions kernel/cgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -1342,21 +1342,22 @@ static int cgroup_show_options(struct seq_file *seq,
if (root != &cgrp_dfl_root)
for_each_subsys(ss, ssid)
if (root->subsys_mask & (1 << ssid))
seq_printf(seq, ",%s", ss->legacy_name);
seq_show_option(seq, ss->name, NULL);
if (root->flags & CGRP_ROOT_NOPREFIX)
seq_puts(seq, ",noprefix");
if (root->flags & CGRP_ROOT_XATTR)
seq_puts(seq, ",xattr");

spin_lock(&release_agent_path_lock);
if (strlen(root->release_agent_path))
seq_printf(seq, ",release_agent=%s", root->release_agent_path);
seq_show_option(seq, "release_agent",
root->release_agent_path);
spin_unlock(&release_agent_path_lock);

if (test_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->cgrp.flags))
seq_puts(seq, ",clone_children");
if (strlen(root->name))
seq_printf(seq, ",name=%s", root->name);
seq_show_option(seq, "name", root->name);
return 0;
}

Expand Down
7 changes: 5 additions & 2 deletions net/ceph/ceph_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,11 @@ int ceph_print_client_options(struct seq_file *m, struct ceph_client *client)
struct ceph_options *opt = client->options;
size_t pos = m->count;

if (opt->name)
seq_printf(m, "name=%s,", opt->name);
if (opt->name) {
seq_puts(m, "name=");
seq_escape(m, opt->name, ", \t\n\\");
seq_putc(m, ',');
}
if (opt->key)
seq_puts(m, "secret=<hidden>,");

Expand Down
2 changes: 1 addition & 1 deletion security/selinux/hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ static void selinux_write_opts(struct seq_file *m,
seq_puts(m, prefix);
if (has_comma)
seq_putc(m, '\"');
seq_puts(m, opts->mnt_opts[i]);
seq_escape(m, opts->mnt_opts[i], "\"\n\\");
if (has_comma)
seq_putc(m, '\"');
}
Expand Down

0 comments on commit a068acf

Please sign in to comment.