]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
fs: create and use seq_show_option for escaping
authorKees Cook <keescook@chromium.org>
Fri, 4 Sep 2015 22:44:57 +0000 (15:44 -0700)
committerZefan Li <lizefan@huawei.com>
Wed, 27 Apr 2016 10:55:18 +0000 (18:55 +0800)
commit a068acf2ee77693e0bf39d6e07139ba704f461c3 upstream.

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.

[akpm@linux-foundation.org: add lost chunk, per Kees]
[keescook@chromium.org: seq_show_option should be using const parameters]
Signed-off-by: Kees Cook <keescook@chromium.org>
Acked-by: Serge Hallyn <serge.hallyn@canonical.com>
Acked-by: Jan Kara <jack@suse.com>
Acked-by: Paul Moore <paul@paul-moore.com>
Cc: J. R. Okajima <hooanon05g@gmail.com>
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[lizf: Backported to 3.4:
 - adjust context
 - one more place in ceph needs to be changed
 - drop changes to overlayfs
 - drop showing vers in cifs]
Signed-off-by: Zefan Li <lizefan@huawei.com>
13 files changed:
fs/ceph/super.c
fs/cifs/cifsfs.c
fs/ext4/super.c
fs/gfs2/super.c
fs/hfs/super.c
fs/hfsplus/options.c
fs/hostfs/hostfs_kern.c
fs/ocfs2/super.c
fs/reiserfs/super.c
fs/xfs/xfs_super.c
include/linux/seq_file.h
kernel/cgroup.c
security/selinux/hooks.c

index f4fa5cf0cdf1d5808a4c8f3685562ab4dd774a84..e5eacd9dd5328c1e8d0487a537793137d3e897f6 100644 (file)
@@ -383,8 +383,10 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root)
        if (opt->flags & CEPH_OPT_NOCRC)
                seq_puts(m, ",nocrc");
 
-       if (opt->name)
-               seq_printf(m, ",name=%s", opt->name);
+       if (opt->name) {
+               seq_puts(m, ",name=");
+               seq_escape(m, opt->name, ", \t\n\\");
+       }
        if (opt->key)
                seq_puts(m, ",secret=<hidden>");
 
@@ -429,7 +431,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;
 }
 
index c0f65e84873eec50260ccc1e4d0a2a557ca8da83..5b730ba78ae37770daed42ff2e27d8dc6a192221 100644 (file)
@@ -373,10 +373,10 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
        if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)
                seq_printf(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;
index 2e26a542c8189470784ce28c64ab052fa9855ba1..3de888c3894acf6b179696101873d49cbf92d52a 100644 (file)
@@ -1682,10 +1682,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]);
 
        if (test_opt(sb, USRQUOTA))
                seq_puts(seq, ",usrquota");
index 6172fa77ad59acf938d7a82f12c6bd1b78735df0..4db9a9a31f29247f113ccf13a643fbac8d198e53 100644 (file)
@@ -1298,11 +1298,11 @@ static int gfs2_show_options(struct seq_file *s, struct dentry *root)
        if (is_ancestor(root, sdp->sd_master_dir))
                seq_printf(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_printf(s, ",spectator");
        if (args->ar_localflocks)
index 7b4c537d6e136b42fba064de39b0c57703b5fec3..be0e218a333ecae9f07fbc3a4f7491e24e8ba907 100644 (file)
@@ -138,9 +138,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", sbi->s_uid, sbi->s_gid);
        if (sbi->s_file_umask != 0133)
                seq_printf(seq, ",file_umask=%o", sbi->s_file_umask);
index 06fa5618600c86c18b033b27a89bea1a91fbeaaf..38e41d07d67f38e6975626a60cf528f5a5558266 100644 (file)
@@ -211,9 +211,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,
                sbi->uid, sbi->gid);
        if (sbi->part >= 0)
index 07c516bfea7671853513d9a80f61f81d5f674f91..fe63b15f54d2d1a5e54ebb291c7ec4951d99dc1f 100644 (file)
@@ -264,7 +264,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);
 
        return 0;
 }
index 68f4541c2db98b26a3aba49234c721e56d56e3c3..91a0020a0ad321ec5f995f5239728d91be37bcc5 100644 (file)
@@ -1578,8 +1578,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)
index 8169be93ac0fc1c595ac4c13cafe4aaa6632d14e..e12357bb309080818f9f32a6086cdda5e3f00210 100644 (file)
@@ -645,18 +645,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) {
index dab9a5f6dfd68345646849f6ca391fa3849839bb..d6c787dc261d7447f9b6616939eb78e9b5655900 100644 (file)
@@ -523,9 +523,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",
index fc61854f62247b9cbaf7bf490d8580f99d54b34e..149b92f055620ee9918d249ceba308ebd546fadc 100644 (file)
@@ -127,6 +127,41 @@ int seq_put_decimal_ull(struct seq_file *m, char delimiter,
 int seq_put_decimal_ll(struct seq_file *m, char delimiter,
                        long long num);
 
+/**
+ * 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
index 34eda955e88743d5d4aabe1691296828e4e61275..7ff5702507a5b65db4698d9bca23ab4adb774952 100644 (file)
@@ -1071,15 +1071,16 @@ static int cgroup_show_options(struct seq_file *seq, struct dentry *dentry)
 
        mutex_lock(&cgroup_root_mutex);
        for_each_subsys(root, ss)
-               seq_printf(seq, ",%s", ss->name);
+               seq_show_option(seq, ss->name, NULL);
        if (test_bit(ROOT_NOPREFIX, &root->flags))
                seq_puts(seq, ",noprefix");
        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);
        if (clone_children(&root->top_cgroup))
                seq_puts(seq, ",clone_children");
        if (strlen(root->name))
-               seq_printf(seq, ",name=%s", root->name);
+               seq_show_option(seq, "name", root->name);
        mutex_unlock(&cgroup_root_mutex);
        return 0;
 }
index cbae6d392087cd05658c899ea52968ce7ab033c9..312d2fb598d7bb59ec3748b178e8ddd1e6a4edde 100644 (file)
@@ -1012,7 +1012,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, '\"');
        }