diff mbox series

[v1] fsconfig: New case cover CVE-2022-0185

Message ID 20230129115021.25778-1-wegao@suse.com
State Changes Requested
Headers show
Series [v1] fsconfig: New case cover CVE-2022-0185 | expand

Commit Message

Wei Gao Jan. 29, 2023, 11:50 a.m. UTC
There are reproducers available for CVE-2022-0185
https://www.openwall.com/lists/oss-security/2022/01/25/14
has links or even a zip file for an exploit
https://github.com/Crusaders-of-Rust/CVE-2022-0185
The exploits are kind of complicated as they try to be complete,
but the exploitation vector is the fsconfig() syscall,
this case used for add some coverage to that to detect it.

Signed-off-by: Wei Gao <wegao@suse.com>
---
 include/lapi/fsmount.h                        |  5 +-
 runtest/syscalls                              |  1 +
 .../kernel/syscalls/fsconfig/fsconfig03.c     | 58 +++++++++++++++++++
 3 files changed, 63 insertions(+), 1 deletion(-)
 create mode 100644 testcases/kernel/syscalls/fsconfig/fsconfig03.c

Comments

Petr Vorel Feb. 1, 2023, 12:49 p.m. UTC | #1
Hi Wei,

...
> +++ b/include/lapi/fsmount.h
> @@ -11,12 +11,15 @@
>  #include "config.h"
>  #include <sys/syscall.h>
>  #include <sys/types.h>
> -#include <sys/mount.h>

>  #ifndef HAVE_FSOPEN
>  # ifdef HAVE_LINUX_MOUNT_H
>  #  include <linux/mount.h>
> +# else
> +#  include <sys/mount.h>
>  # endif
> +#else
> +# include <sys/mount.h>
>  #endif
Does <linux/mount.h> conflicts with <sys/mount.h>? Or why is this needed?

>  #include "lapi/fcntl.h"
> diff --git a/runtest/syscalls b/runtest/syscalls
> index ae37a1192..b4cde8071 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -383,6 +383,7 @@ fremovexattr02 fremovexattr02

>  fsconfig01 fsconfig01
>  fsconfig02 fsconfig02
> +fsconfig03 fsconfig03

NOTE: you also need to add a new record in testcases/kernel/syscalls/fsconfig/.gitignore.

> diff --git a/testcases/kernel/syscalls/fsconfig/fsconfig03.c b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
> new file mode 100644
> index 000000000..e076c2f09
> --- /dev/null
> +++ b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
> @@ -0,0 +1,58 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2023 Wei Gao <wegao@suse.com>
> + */
> +
> +/*\
NOTE, there should be docparse label:
 * [Description]
> + * Test add some coverage to CVE-2022-0185.
> + * Try to trigger a crash.
> + * References links:
> + * https://www.openwall.com/lists/oss-security/2022/01/25/14
> + * https://github.com/Crusaders-of-Rust/CVE-2022-0185
> + */
> +
> +#include "tst_test.h"
> +#include "lapi/fsmount.h"
> +
> +#define MNTPOINT	"mntpoint"
> +
> +static int fd = -1;
> +
> +static void setup(void)
> +{
> +	fsopen_supported_by_kernel();
> +
> +	TEST(fd = fsopen(tst_device->fs_type, 0));
> +	if (fd == -1)
> +		tst_brk(TBROK | TTERRNO, "fsopen() failed");
Sooner or later we should add SAFE_FSOPEN(), but that can wait.

> +
> +}
> +
> +static void cleanup(void)
> +{
> +	if (fd != -1)
> +		SAFE_CLOSE(fd);
> +}
> +
> +static void run(void)
> +{
> +	char *val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
> +
> +	for (unsigned int i = 0; i < 2; i++) {
> +		TEST(fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0));
> +		if (TST_RET == -1)
> +			tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_SET_STRING) failed");
TST_EXP_PASS() or other could here be used (it should be changes also in fsconfig01.c).

Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:

tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
tst_test.c:1634: TINFO: === Testing on ext3 ===
tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
mke2fs 1.46.5 (30-Dec-2021)
fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)

Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
used?

> +	}
> +	tst_res(TPASS, "Try fsconfig overflow on %s done!", tst_device->fs_type);
> +}
> +
> +static struct tst_test test = {
> +	.test_all = run,
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.needs_root = 1,
> +	.format_device = 1,
> +	.mntpoint = MNTPOINT,
> +	.all_filesystems = 1,
> +	.skip_filesystems = (const char *const []){"fuse", "ext2", "xfs", "tmpfs", NULL},

I wonder why this is should not be run on XFS and ext2.

Also, while we have CVE and kernel fix [1], it should be marked in struct tst_test:

	.tags = (const struct tst_tag[]) {
		{"linux-git", "722d94847de2"},
		{"CVE", "2020-29373"},
		{"CVE", "2022-0185"},
		{}
	}

Kind regards,
Petr

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=722d94847de2


> +};
Wei Gao Feb. 6, 2023, 10:38 a.m. UTC | #2
On Wed, Feb 01, 2023 at 01:49:43PM +0100, Petr Vorel wrote:
> Hi Wei,
> 
> ...
> > +++ b/include/lapi/fsmount.h
> > @@ -11,12 +11,15 @@
> >  #include "config.h"
> >  #include <sys/syscall.h>
> >  #include <sys/types.h>
> > -#include <sys/mount.h>
> 
> >  #ifndef HAVE_FSOPEN
> >  # ifdef HAVE_LINUX_MOUNT_H
> >  #  include <linux/mount.h>
> > +# else
> > +#  include <sys/mount.h>
> >  # endif
> > +#else
> > +# include <sys/mount.h>
> >  #endif
> Does <linux/mount.h> conflicts with <sys/mount.h>? Or why is this needed?
> 
> >  #include "lapi/fcntl.h"
> > diff --git a/runtest/syscalls b/runtest/syscalls
> > index ae37a1192..b4cde8071 100644
> > --- a/runtest/syscalls
> > +++ b/runtest/syscalls
> > @@ -383,6 +383,7 @@ fremovexattr02 fremovexattr02
> 
> >  fsconfig01 fsconfig01
> >  fsconfig02 fsconfig02
> > +fsconfig03 fsconfig03
> 
> NOTE: you also need to add a new record in testcases/kernel/syscalls/fsconfig/.gitignore.
> 
> > diff --git a/testcases/kernel/syscalls/fsconfig/fsconfig03.c b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
> > new file mode 100644
> > index 000000000..e076c2f09
> > --- /dev/null
> > +++ b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
> > @@ -0,0 +1,58 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Copyright (c) 2023 Wei Gao <wegao@suse.com>
> > + */
> > +
> > +/*\
> NOTE, there should be docparse label:
>  * [Description]
> > + * Test add some coverage to CVE-2022-0185.
> > + * Try to trigger a crash.
> > + * References links:
> > + * https://www.openwall.com/lists/oss-security/2022/01/25/14
> > + * https://github.com/Crusaders-of-Rust/CVE-2022-0185
> > + */
> > +
> > +#include "tst_test.h"
> > +#include "lapi/fsmount.h"
> > +
> > +#define MNTPOINT	"mntpoint"
> > +
> > +static int fd = -1;
> > +
> > +static void setup(void)
> > +{
> > +	fsopen_supported_by_kernel();
> > +
> > +	TEST(fd = fsopen(tst_device->fs_type, 0));
> > +	if (fd == -1)
> > +		tst_brk(TBROK | TTERRNO, "fsopen() failed");
> Sooner or later we should add SAFE_FSOPEN(), but that can wait.
> 
> > +
> > +}
> > +
> > +static void cleanup(void)
> > +{
> > +	if (fd != -1)
> > +		SAFE_CLOSE(fd);
> > +}
> > +
> > +static void run(void)
> > +{
> > +	char *val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
> > +
> > +	for (unsigned int i = 0; i < 2; i++) {
> > +		TEST(fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0));
> > +		if (TST_RET == -1)
> > +			tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_SET_STRING) failed");
> TST_EXP_PASS() or other could here be used (it should be changes also in fsconfig01.c).
> 
> Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:
> 
> tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
> tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
> tst_test.c:1634: TINFO: === Testing on ext3 ===
> tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
> mke2fs 1.46.5 (30-Dec-2021)
> fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)
> 
> Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
> used?
> 
I have not test on 6.2.0 kernel, i need reproduce this firstly.
> > +	}
> > +	tst_res(TPASS, "Try fsconfig overflow on %s done!", tst_device->fs_type);
> > +}
> > +
> > +static struct tst_test test = {
> > +	.test_all = run,
> > +	.setup = setup,
> > +	.cleanup = cleanup,
> > +	.needs_root = 1,
> > +	.format_device = 1,
> > +	.mntpoint = MNTPOINT,
> > +	.all_filesystems = 1,
> > +	.skip_filesystems = (const char *const []){"fuse", "ext2", "xfs", "tmpfs", NULL},
> 
> I wonder why this is should not be run on XFS and ext2.
ext2: this if failed on one of my specific machine, so normally this should run.
xfs: this always error happen with error code "TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)", i 
need debug kernel to check what's happen.
> 
> Also, while we have CVE and kernel fix [1], it should be marked in struct tst_test:
> 
> 	.tags = (const struct tst_tag[]) {
> 		{"linux-git", "722d94847de2"},
> 		{"CVE", "2020-29373"},
> 		{"CVE", "2022-0185"},
> 		{}
> 	}
> 
> Kind regards,
> Petr
> 
> [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=722d94847de2
> 
> 
> > +};
Petr Vorel Feb. 6, 2023, 4:19 p.m. UTC | #3
Hi Wei,

...
> > Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:

> > tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
> > tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
> > tst_test.c:1634: TINFO: === Testing on ext3 ===
> > tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
> > mke2fs 1.46.5 (30-Dec-2021)
> > fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)

> > Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
> > used?

> I have not test on 6.2.0 kernel, i need reproduce this firstly.

FYI 6.0.6 is also broken, works on 5.10.46.

Kind regards,
Petr
Wei Gao Feb. 6, 2023, 4:42 p.m. UTC | #4
On Mon, Feb 06, 2023 at 05:38:18AM -0500, Wei Gao via ltp wrote:
> On Wed, Feb 01, 2023 at 01:49:43PM +0100, Petr Vorel wrote:
> > Hi Wei,
> > 
> > ...
> > > +++ b/include/lapi/fsmount.h
> > > @@ -11,12 +11,15 @@
> > >  #include "config.h"
> > >  #include <sys/syscall.h>
> > >  #include <sys/types.h>
> > > -#include <sys/mount.h>
> > 
> > >  #ifndef HAVE_FSOPEN
> > >  # ifdef HAVE_LINUX_MOUNT_H
> > >  #  include <linux/mount.h>
> > > +# else
> > > +#  include <sys/mount.h>
> > >  # endif
> > > +#else
> > > +# include <sys/mount.h>
> > >  #endif
> > Does <linux/mount.h> conflicts with <sys/mount.h>? Or why is this needed?
> > 
> > >  #include "lapi/fcntl.h"
> > > diff --git a/runtest/syscalls b/runtest/syscalls
> > > index ae37a1192..b4cde8071 100644
> > > --- a/runtest/syscalls
> > > +++ b/runtest/syscalls
> > > @@ -383,6 +383,7 @@ fremovexattr02 fremovexattr02
> > 
> > >  fsconfig01 fsconfig01
> > >  fsconfig02 fsconfig02
> > > +fsconfig03 fsconfig03
> > 
> > NOTE: you also need to add a new record in testcases/kernel/syscalls/fsconfig/.gitignore.
> > 
> > > diff --git a/testcases/kernel/syscalls/fsconfig/fsconfig03.c b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
> > > new file mode 100644
> > > index 000000000..e076c2f09
> > > --- /dev/null
> > > +++ b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
> > > @@ -0,0 +1,58 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * Copyright (c) 2023 Wei Gao <wegao@suse.com>
> > > + */
> > > +
> > > +/*\
> > NOTE, there should be docparse label:
> >  * [Description]
> > > + * Test add some coverage to CVE-2022-0185.
> > > + * Try to trigger a crash.
> > > + * References links:
> > > + * https://www.openwall.com/lists/oss-security/2022/01/25/14
> > > + * https://github.com/Crusaders-of-Rust/CVE-2022-0185
> > > + */
> > > +
> > > +#include "tst_test.h"
> > > +#include "lapi/fsmount.h"
> > > +
> > > +#define MNTPOINT	"mntpoint"
> > > +
> > > +static int fd = -1;
> > > +
> > > +static void setup(void)
> > > +{
> > > +	fsopen_supported_by_kernel();
> > > +
> > > +	TEST(fd = fsopen(tst_device->fs_type, 0));
> > > +	if (fd == -1)
> > > +		tst_brk(TBROK | TTERRNO, "fsopen() failed");
> > Sooner or later we should add SAFE_FSOPEN(), but that can wait.
> > 
> > > +
> > > +}
> > > +
> > > +static void cleanup(void)
> > > +{
> > > +	if (fd != -1)
> > > +		SAFE_CLOSE(fd);
> > > +}
> > > +
> > > +static void run(void)
> > > +{
> > > +	char *val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
> > > +
> > > +	for (unsigned int i = 0; i < 2; i++) {
> > > +		TEST(fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0));
> > > +		if (TST_RET == -1)
> > > +			tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_SET_STRING) failed");
> > TST_EXP_PASS() or other could here be used (it should be changes also in fsconfig01.c).
> > 
> > Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:
> > 
> > tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
> > tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
> > tst_test.c:1634: TINFO: === Testing on ext3 ===
> > tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
> > mke2fs 1.46.5 (30-Dec-2021)
> > fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)
> > 
> > Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
> > used?
> > 
> I have not test on 6.2.0 kernel, i need reproduce this firstly.
> > > +	}
> > > +	tst_res(TPASS, "Try fsconfig overflow on %s done!", tst_device->fs_type);
> > > +}
> > > +
> > > +static struct tst_test test = {
> > > +	.test_all = run,
> > > +	.setup = setup,
> > > +	.cleanup = cleanup,
> > > +	.needs_root = 1,
> > > +	.format_device = 1,
> > > +	.mntpoint = MNTPOINT,
> > > +	.all_filesystems = 1,
> > > +	.skip_filesystems = (const char *const []){"fuse", "ext2", "xfs", "tmpfs", NULL},
> > 
> > I wonder why this is should not be run on XFS and ext2.
> ext2: this if failed on one of my specific machine, so normally this should run.
> xfs: this always error happen with error code "TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)", i 
> need debug kernel to check what's happen.

Update xfs investigation progress:
After some debug check ths kernel file system code, i found xfs ONLY can accept key which defined in xfs_fs_parameters, so
EINVAL returned.

===xfs kernel logic tracking detail start ===

SYSCALL_DEFINE5(fsconfig   fs/fsopen.c
	vfs_fsconfig_locked
		vfs_parse_fs_param
				ret = fc->ops->parse_param(fc, param);  // this is xfs_fs_parse_param				
					xfs_fs_parse_param
						fs_parse 
							__fs_parse
		
103     int __fs_parse(struct p_log *log,
104                  const struct fs_parameter_spec *desc,
105                  struct fs_parameter *param,
106                  struct fs_parse_result *result)
107     {
108             const struct fs_parameter_spec *p;
109
110             result->uint_64 = 0;
111
(gdb) l
112             p = fs_lookup_key(desc, param, &result->negated);
113             if (!p)
114                     return -ENOPARAM;   <==== this error


static const struct fs_parameter_spec xfs_fs_parameters[] = {
        fsparam_u32("logbufs",          Opt_logbufs),
        fsparam_string("logbsize",      Opt_logbsize),
        fsparam_string("logdev",        Opt_logdev),  <=== ONLY can accept this table as KEY!!
        fsparam_string("rtdev",         Opt_rtdev),
        fsparam_flag("wsync",           Opt_wsync),

===xfs kernel logic tracking detail end===

> > 
> > Also, while we have CVE and kernel fix [1], it should be marked in struct tst_test:
> > 
> > 	.tags = (const struct tst_tag[]) {
> > 		{"linux-git", "722d94847de2"},
> > 		{"CVE", "2020-29373"},
> > 		{"CVE", "2022-0185"},
> > 		{}
> > 	}
> > 
> > Kind regards,
> > Petr
> > 
> > [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=722d94847de2
> > 
> > 
> > > +};
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp
Wei Gao Feb. 8, 2023, 9:01 a.m. UTC | #5
On Mon, Feb 06, 2023 at 05:19:53PM +0100, Petr Vorel wrote:
> Hi Wei,
> 
> ...
> > > Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:
> 
> > > tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
> > > tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
> > > tst_test.c:1634: TINFO: === Testing on ext3 ===
> > > tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
> > > mke2fs 1.46.5 (30-Dec-2021)
> > > fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)
> 
> > > Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
> > > used?
> 
> > I have not test on 6.2.0 kernel, i need reproduce this firstly.
> 
> FYI 6.0.6 is also broken, works on 5.10.46.
> 

After long investigation i finally get what's happen now since i have
never touch kernel fs code before :)

The root caused is cebe85d570cf8 which make udpate initialize of
ext3_fs_type.

Each file system will initialize a struct file_system_type and ext3 initialize 
in fs/ext4/super.c(maybe ext3 much same as ext4 so they put in same file).
This patch add new memeber .init_fs_context in ext3 file_system_type struct and 
this new member will lead pase function which called by fsconfig change
from legacy_parse_param to ext4_parse_param(this function will check
parameter and not allow 0x00)

===key change part of cebe85d570cf8===
 static struct file_system_type ext3_fs_type = {
-       .owner          = THIS_MODULE,
-       .name           = "ext3",
-       .mount          = ext4_mount,
-       .kill_sb        = kill_block_super,
-       .fs_flags       = FS_REQUIRES_DEV,
+       .owner                  = THIS_MODULE,
+       .name                   = "ext3",
+       .init_fs_context        = ext4_init_fs_context,  // in this patch init_fs_context start set ext4_init_fs_context
+       .parameters             = ext4_param_specs,
+       .kill_sb                = kill_block_super,
+       .fs_flags               = FS_REQUIRES_DEV,
 };
===key change part of cebe85d570cf8===


Following logic will decide whether use legacy_init_fs_context base on
exist of init_fs_context, obviously before patch we have no
init_fs_context but after patch we have it

==function alloc_fs_context== 
        .....
        init_fs_context = fc->fs_type->init_fs_context;
        if (!init_fs_context)
                init_fs_context = legacy_init_fs_context;  //before patch cebe85d570cf8, legacy_init_fs_context will be set.
        ret = init_fs_context(fc);

==function alloc_fs_context== 


====code example for set parse function used by fsconfig===
const struct fs_context_operations legacy_fs_context_ops = {
        .free                   = legacy_fs_context_free,
        .dup                    = legacy_fs_context_dup,
        .parse_param            = legacy_parse_param,   
        .parse_monolithic       = legacy_parse_monolithic,
        .get_tree               = legacy_get_tree,
        .reconfigure            = legacy_reconfigure,
};

/*
 * Initialise a legacy context for a filesystem that doesn't support
 * fs_context.
 */
static int legacy_init_fs_context(struct fs_context *fc)
{
        fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL_ACCOUNT);
        if (!fc->fs_private)
                return -ENOMEM;
        fc->ops = &legacy_fs_context_ops;
        return 0;
}
====code for set parse function used by fsconfig===


====final call parse function within fsconfig logic==
vfs_parse_fs_param
145             if (fc->ops->parse_param) {
146                     	ret = fc->ops->parse_param(fc, param); //this will call legacy_parse_param or ext4_parse_param                                   
147                     if (ret != -ENOPARAM)
148                             return ret;
149             }
====final call parse function within fsconfig logic==



Just FYI the fs_type real data show in GDB(init_fs_context= 0 in kernel5.x but in kernel 6.x init_fs_context=ext4_parse_param):

(gdb) p *fs_type
$4 = {name = 0xffffffff822278e1 "ext3", fs_flags = 1, init_fs_context = 0x0 <fixed_percpu_data>, parameters = 0x0 <fixed_percpu_data>,
  mount = 0xffffffff812ec510 <ext4_mount>, kill_sb = 0xffffffff811f7220 <kill_block_super>, owner = 0x0 <fixed_percpu_data>,
  next = 0xffffffff82564600 <ext2_fs_type>, fs_supers = {first = 0x0 <fixed_percpu_data>}, s_lock_key = {<No data fields>},
  s_umount_key = {<No data fields>}, s_vfs_rename_key = {<No data fields>}, s_writers_key = 0xffffffff825645e8, i_lock_key = {<No data fields>},
  i_mutex_key = {<No data fields>}, invalidate_lock_key = {<No data fields>}, i_mutex_dir_key = {<No data fields>}}


> Kind regards,
> Petr
Petr Vorel Feb. 8, 2023, 3:48 p.m. UTC | #6
> On Mon, Feb 06, 2023 at 05:19:53PM +0100, Petr Vorel wrote:
> > Hi Wei,

> > ...
> > > > Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:

> > > > tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
> > > > tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
> > > > tst_test.c:1634: TINFO: === Testing on ext3 ===
> > > > tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
> > > > mke2fs 1.46.5 (30-Dec-2021)
> > > > fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)

> > > > Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
> > > > used?

> > > I have not test on 6.2.0 kernel, i need reproduce this firstly.

> > FYI 6.0.6 is also broken, works on 5.10.46.


> After long investigation i finally get what's happen now since i have
> never touch kernel fs code before :)

> The root caused is cebe85d570cf8 which make udpate initialize of
> ext3_fs_type.
What exactly happen in cebe85d570cf ("ext4: switch to the new mount api")
from v5.17-rc1? This enables fsconfig, thus test *should* be working on >=
v5.17-rc1, right? But it does not.

BTW xfs also uses new mount API 73e5fff98b64 ("xfs: switch to use the new
mount-api") in v5.5-rc1.  Obviously legacy ext2 driver does not use it.

I'd expect fsopen_supported_by_kernel() is enough and no filesystem needs to be
filtered out (at least not real linux filesystems like ext2 or xfs).

> Each file system will initialize a struct file_system_type and ext3 initialize 
> in fs/ext4/super.c(maybe ext3 much same as ext4 so they put in same file).
Yes, ext3 and ext4 share the same code (ext4 driver). This code also servers
ext2 filesystem if CONFIG_EXT4_USE_FOR_EXT2=y (mostly the default), otherwise
there is ext2 driver: CONFIG_EXT2_FS).

Kind regards,
Petr

> This patch add new memeber .init_fs_context in ext3 file_system_type struct and 
> this new member will lead pase function which called by fsconfig change
> from legacy_parse_param to ext4_parse_param(this function will check
> parameter and not allow 0x00)

> ===key change part of cebe85d570cf8===
>  static struct file_system_type ext3_fs_type = {
> -       .owner          = THIS_MODULE,
> -       .name           = "ext3",
> -       .mount          = ext4_mount,
> -       .kill_sb        = kill_block_super,
> -       .fs_flags       = FS_REQUIRES_DEV,
> +       .owner                  = THIS_MODULE,
> +       .name                   = "ext3",
> +       .init_fs_context        = ext4_init_fs_context,  // in this patch init_fs_context start set ext4_init_fs_context
> +       .parameters             = ext4_param_specs,
> +       .kill_sb                = kill_block_super,
> +       .fs_flags               = FS_REQUIRES_DEV,
>  };
> ===key change part of cebe85d570cf8===


> Following logic will decide whether use legacy_init_fs_context base on
> exist of init_fs_context, obviously before patch we have no
> init_fs_context but after patch we have it

> ==function alloc_fs_context== 
>         .....
>         init_fs_context = fc->fs_type->init_fs_context;
>         if (!init_fs_context)
>                 init_fs_context = legacy_init_fs_context;  //before patch cebe85d570cf8, legacy_init_fs_context will be set.
>         ret = init_fs_context(fc);

> ==function alloc_fs_context== 


> ====code example for set parse function used by fsconfig===
> const struct fs_context_operations legacy_fs_context_ops = {
>         .free                   = legacy_fs_context_free,
>         .dup                    = legacy_fs_context_dup,
>         .parse_param            = legacy_parse_param,   
>         .parse_monolithic       = legacy_parse_monolithic,
>         .get_tree               = legacy_get_tree,
>         .reconfigure            = legacy_reconfigure,
> };

> /*
>  * Initialise a legacy context for a filesystem that doesn't support
>  * fs_context.
>  */
> static int legacy_init_fs_context(struct fs_context *fc)
> {
>         fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL_ACCOUNT);
>         if (!fc->fs_private)
>                 return -ENOMEM;
>         fc->ops = &legacy_fs_context_ops;
>         return 0;
> }
> ====code for set parse function used by fsconfig===


> ====final call parse function within fsconfig logic==
> vfs_parse_fs_param
> 145             if (fc->ops->parse_param) {
> 146                     	ret = fc->ops->parse_param(fc, param); //this will call legacy_parse_param or ext4_parse_param                                   
> 147                     if (ret != -ENOPARAM)
> 148                             return ret;
> 149             }
> ====final call parse function within fsconfig logic==



> Just FYI the fs_type real data show in GDB(init_fs_context= 0 in kernel5.x but in kernel 6.x init_fs_context=ext4_parse_param):

> (gdb) p *fs_type
> $4 = {name = 0xffffffff822278e1 "ext3", fs_flags = 1, init_fs_context = 0x0 <fixed_percpu_data>, parameters = 0x0 <fixed_percpu_data>,
>   mount = 0xffffffff812ec510 <ext4_mount>, kill_sb = 0xffffffff811f7220 <kill_block_super>, owner = 0x0 <fixed_percpu_data>,
>   next = 0xffffffff82564600 <ext2_fs_type>, fs_supers = {first = 0x0 <fixed_percpu_data>}, s_lock_key = {<No data fields>},
>   s_umount_key = {<No data fields>}, s_vfs_rename_key = {<No data fields>}, s_writers_key = 0xffffffff825645e8, i_lock_key = {<No data fields>},
>   i_mutex_key = {<No data fields>}, invalidate_lock_key = {<No data fields>}, i_mutex_dir_key = {<No data fields>}}


> > Kind regards,
> > Petr
Wei Gao Feb. 9, 2023, 2:25 a.m. UTC | #7
On Wed, Feb 08, 2023 at 04:48:23PM +0100, Petr Vorel wrote:
> > On Mon, Feb 06, 2023 at 05:19:53PM +0100, Petr Vorel wrote:
> > > Hi Wei,
> 
> > > ...
> > > > > Hm, there is a kernel fix from 5.17 [1]. But test fails when I run it on 6.2.0-rc5:
> 
> > > > > tst_supported_fs_types.c:165: TINFO: Skipping FUSE based ntfs as requested by the test
> > > > > tst_supported_fs_types.c:157: TINFO: Skipping tmpfs as requested by the test
> > > > > tst_test.c:1634: TINFO: === Testing on ext3 ===
> > > > > tst_test.c:1093: TINFO: Formatting /dev/loop0 with ext3 opts='' extra opts=''
> > > > > mke2fs 1.46.5 (30-Dec-2021)
> > > > > fsconfig03.c:44: TFAIL: fsconfig(FSCONFIG_SET_STRING) failed: EINVAL (22)
> 
> > > > > Isn't it the opposite: we expect to fail, thus TST_EXP_FAIL() should here be
> > > > > used?
> 
> > > > I have not test on 6.2.0 kernel, i need reproduce this firstly.
> 
> > > FYI 6.0.6 is also broken, works on 5.10.46.
> 
> 
> > After long investigation i finally get what's happen now since i have
> > never touch kernel fs code before :)
> 
> > The root caused is cebe85d570cf8 which make udpate initialize of
> > ext3_fs_type.
> What exactly happen in cebe85d570cf ("ext4: switch to the new mount api")
> from v5.17-rc1? This enables fsconfig, thus test *should* be working on >=
> v5.17-rc1, right? But it does not.
> 

In my test case use "\x00" as fsconfig key parameter, this parameter ONLY
can be accept if kernel using legacy_parse_param. But cebe85d570cf8 make
a change lead kernel start using ext4_parse_param then error will happen
in your test in v5.17, it's normal behaviro.

So the correct behaviro are:
1)you will encounter crash if you running my ltp test case before v5.17. 
2)if you test from v5.17, the security fix 722d94847de29 together with
internal logic change cebe85d570cf8 both happen, and test case will
failed since cebe85d570cf8 change the whole logic and workaround the
security fix 722d94847de29(More detail please see below summary). 

Why 1) not happen now in your test env? 
Since currently my v1 patch has a mistake on loop i number, if you increase loop number i to
5000 then you will encounter crash on kernel before v5.17(ext2/3/4).

+       for (unsigned int i = 0; i < 2; i++) { <====increase this to i < 5000
+               TEST(fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0));
+               if (TST_RET == -1)
+                       tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_SET_STRING) failed");
+       }



Let me explain more detail for this:

CVE-2022-0185 security bug popped up since 5.1-rc1 and fixed by 722d94847de29 in v5.17-rc1~50, so normally we should check build from v5.17.
Most important thing is this security issue ONLY happen if fsconfig go through legacy_parse_param function(security issue happen and fixed within this function).

But:
For xfs filesystem, from v5.5-rc1 already start use xfs_fs_parse_param instead of  legacy_parse_param, so make no sense check this secruity issue
For ext2&ext3&ext4, after patch cebe85d570cf8 in v5.17-rc1~131^2~36, use ext4_parse_param instead of legacy_parse_param, so also make no sense check 

In summary, we can reject this test case since from v5.17, ext2/ext4/xfs not go through legacy_parse_param and means we can not verify security fix 
722d94847de29(this fix happen in legacy_parse_param.)


Just FYI : fix bug patch which within legacy_parse_param
722d94847de29 (Jamie Hill-Daniel   2022-01-18 08:06:04 +0100 551)       if (size + len + 2 > PAGE_SIZE)
git describe --contains 722d94847de29
v5.17-rc1~50


> BTW xfs also uses new mount API 73e5fff98b64 ("xfs: switch to use the new
> mount-api") in v5.5-rc1.  Obviously legacy ext2 driver does not use it.
> 
> I'd expect fsopen_supported_by_kernel() is enough and no filesystem needs to be
> filtered out (at least not real linux filesystems like ext2 or xfs).
> 
> > Each file system will initialize a struct file_system_type and ext3 initialize 
> > in fs/ext4/super.c(maybe ext3 much same as ext4 so they put in same file).
> Yes, ext3 and ext4 share the same code (ext4 driver). This code also servers
> ext2 filesystem if CONFIG_EXT4_USE_FOR_EXT2=y (mostly the default), otherwise
> there is ext2 driver: CONFIG_EXT2_FS).
> 
> Kind regards,
> Petr
> 
> > This patch add new memeber .init_fs_context in ext3 file_system_type struct and 
> > this new member will lead pase function which called by fsconfig change
> > from legacy_parse_param to ext4_parse_param(this function will check
> > parameter and not allow 0x00)
> 
> > ===key change part of cebe85d570cf8===
> >  static struct file_system_type ext3_fs_type = {
> > -       .owner          = THIS_MODULE,
> > -       .name           = "ext3",
> > -       .mount          = ext4_mount,
> > -       .kill_sb        = kill_block_super,
> > -       .fs_flags       = FS_REQUIRES_DEV,
> > +       .owner                  = THIS_MODULE,
> > +       .name                   = "ext3",
> > +       .init_fs_context        = ext4_init_fs_context,  // in this patch init_fs_context start set ext4_init_fs_context
> > +       .parameters             = ext4_param_specs,
> > +       .kill_sb                = kill_block_super,
> > +       .fs_flags               = FS_REQUIRES_DEV,
> >  };
> > ===key change part of cebe85d570cf8===
> 
> 
> > Following logic will decide whether use legacy_init_fs_context base on
> > exist of init_fs_context, obviously before patch we have no
> > init_fs_context but after patch we have it
> 
> > ==function alloc_fs_context== 
> >         .....
> >         init_fs_context = fc->fs_type->init_fs_context;
> >         if (!init_fs_context)
> >                 init_fs_context = legacy_init_fs_context;  //before patch cebe85d570cf8, legacy_init_fs_context will be set.
> >         ret = init_fs_context(fc);
> 
> > ==function alloc_fs_context== 
> 
> 
> > ====code example for set parse function used by fsconfig===
> > const struct fs_context_operations legacy_fs_context_ops = {
> >         .free                   = legacy_fs_context_free,
> >         .dup                    = legacy_fs_context_dup,
> >         .parse_param            = legacy_parse_param,   
> >         .parse_monolithic       = legacy_parse_monolithic,
> >         .get_tree               = legacy_get_tree,
> >         .reconfigure            = legacy_reconfigure,
> > };
> 
> > /*
> >  * Initialise a legacy context for a filesystem that doesn't support
> >  * fs_context.
> >  */
> > static int legacy_init_fs_context(struct fs_context *fc)
> > {
> >         fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL_ACCOUNT);
> >         if (!fc->fs_private)
> >                 return -ENOMEM;
> >         fc->ops = &legacy_fs_context_ops;
> >         return 0;
> > }
> > ====code for set parse function used by fsconfig===
> 
> 
> > ====final call parse function within fsconfig logic==
> > vfs_parse_fs_param
> > 145             if (fc->ops->parse_param) {
> > 146                     	ret = fc->ops->parse_param(fc, param); //this will call legacy_parse_param or ext4_parse_param                                   
> > 147                     if (ret != -ENOPARAM)
> > 148                             return ret;
> > 149             }
> > ====final call parse function within fsconfig logic==
> 
> 
> 
> > Just FYI the fs_type real data show in GDB(init_fs_context= 0 in kernel5.x but in kernel 6.x init_fs_context=ext4_parse_param):
> 
> > (gdb) p *fs_type
> > $4 = {name = 0xffffffff822278e1 "ext3", fs_flags = 1, init_fs_context = 0x0 <fixed_percpu_data>, parameters = 0x0 <fixed_percpu_data>,
> >   mount = 0xffffffff812ec510 <ext4_mount>, kill_sb = 0xffffffff811f7220 <kill_block_super>, owner = 0x0 <fixed_percpu_data>,
> >   next = 0xffffffff82564600 <ext2_fs_type>, fs_supers = {first = 0x0 <fixed_percpu_data>}, s_lock_key = {<No data fields>},
> >   s_umount_key = {<No data fields>}, s_vfs_rename_key = {<No data fields>}, s_writers_key = 0xffffffff825645e8, i_lock_key = {<No data fields>},
> >   i_mutex_key = {<No data fields>}, invalidate_lock_key = {<No data fields>}, i_mutex_dir_key = {<No data fields>}}
> 
> 
> > > Kind regards,
> > > Petr
Cyril Hrubis Feb. 9, 2023, 10:10 a.m. UTC | #8
Hi!
> Let me explain more detail for this:
> 
> CVE-2022-0185 security bug popped up since 5.1-rc1 and fixed by 722d94847de29 in v5.17-rc1~50, so normally we should check build from v5.17.
> Most important thing is this security issue ONLY happen if fsconfig go through legacy_parse_param function(security issue happen and fixed within this function).
>
> But:
> For xfs filesystem, from v5.5-rc1 already start use xfs_fs_parse_param instead of  legacy_parse_param, so make no sense check this secruity issue
> For ext2&ext3&ext4, after patch cebe85d570cf8 in v5.17-rc1~131^2~36, use ext4_parse_param instead of legacy_parse_param, so also make no sense check 
> 
> In summary, we can reject this test case since from v5.17, ext2/ext4/xfs not go through legacy_parse_param and means we can not verify security fix 
> 722d94847de29(this fix happen in legacy_parse_param.)

Quite contrary it make sense to add regression tests for kernel and keep them
running on all filesystems and never releases since you never know when
similar mistake will make it into the kernel code again. It does not
make much sense to invest time into tests only to keep them disabled
later on.

More generally it makes sense to try to throw all kind of garbage
strings into fsconfig() and expect to get EINVAL or other sane behavior,
writing such tests is the only way to avoid or at least catch most CVEs
before they happen.
Wei Gao Feb. 9, 2023, 11:37 a.m. UTC | #9
On Thu, Feb 09, 2023 at 11:10:46AM +0100, Cyril Hrubis wrote:
> Hi!
> > Let me explain more detail for this:
> > 
> > CVE-2022-0185 security bug popped up since 5.1-rc1 and fixed by 722d94847de29 in v5.17-rc1~50, so normally we should check build from v5.17.
> > Most important thing is this security issue ONLY happen if fsconfig go through legacy_parse_param function(security issue happen and fixed within this function).
> >
> > But:
> > For xfs filesystem, from v5.5-rc1 already start use xfs_fs_parse_param instead of  legacy_parse_param, so make no sense check this secruity issue
> > For ext2&ext3&ext4, after patch cebe85d570cf8 in v5.17-rc1~131^2~36, use ext4_parse_param instead of legacy_parse_param, so also make no sense check 
> > 
> > In summary, we can reject this test case since from v5.17, ext2/ext4/xfs not go through legacy_parse_param and means we can not verify security fix 
> > 722d94847de29(this fix happen in legacy_parse_param.)
> 
> Quite contrary it make sense to add regression tests for kernel and keep them
> running on all filesystems and never releases since you never know when
> similar mistake will make it into the kernel code again. It does not
> make much sense to invest time into tests only to keep them disabled
> later on.
> 
> More generally it makes sense to try to throw all kind of garbage
> strings into fsconfig() and expect to get EINVAL or other sane behavior,
> writing such tests is the only way to avoid or at least catch most CVEs
> before they happen.
> 

Thanks for review this, i will update the case later.

> -- 
> Cyril Hrubis
> chrubis@suse.cz
diff mbox series

Patch

diff --git a/include/lapi/fsmount.h b/include/lapi/fsmount.h
index 07eb42ffa..252accb0f 100644
--- a/include/lapi/fsmount.h
+++ b/include/lapi/fsmount.h
@@ -11,12 +11,15 @@ 
 #include "config.h"
 #include <sys/syscall.h>
 #include <sys/types.h>
-#include <sys/mount.h>
 
 #ifndef HAVE_FSOPEN
 # ifdef HAVE_LINUX_MOUNT_H
 #  include <linux/mount.h>
+# else
+#  include <sys/mount.h>
 # endif
+#else
+# include <sys/mount.h>
 #endif
 
 #include "lapi/fcntl.h"
diff --git a/runtest/syscalls b/runtest/syscalls
index ae37a1192..b4cde8071 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -383,6 +383,7 @@  fremovexattr02 fremovexattr02
 
 fsconfig01 fsconfig01
 fsconfig02 fsconfig02
+fsconfig03 fsconfig03
 
 fsmount01 fsmount01
 fsmount02 fsmount02
diff --git a/testcases/kernel/syscalls/fsconfig/fsconfig03.c b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
new file mode 100644
index 000000000..e076c2f09
--- /dev/null
+++ b/testcases/kernel/syscalls/fsconfig/fsconfig03.c
@@ -0,0 +1,58 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Wei Gao <wegao@suse.com>
+ */
+
+/*\
+ * Test add some coverage to CVE-2022-0185.
+ * Try to trigger a crash.
+ * References links:
+ * https://www.openwall.com/lists/oss-security/2022/01/25/14
+ * https://github.com/Crusaders-of-Rust/CVE-2022-0185
+ */
+
+#include "tst_test.h"
+#include "lapi/fsmount.h"
+
+#define MNTPOINT	"mntpoint"
+
+static int fd = -1;
+
+static void setup(void)
+{
+	fsopen_supported_by_kernel();
+
+	TEST(fd = fsopen(tst_device->fs_type, 0));
+	if (fd == -1)
+		tst_brk(TBROK | TTERRNO, "fsopen() failed");
+
+}
+
+static void cleanup(void)
+{
+	if (fd != -1)
+		SAFE_CLOSE(fd);
+}
+
+static void run(void)
+{
+	char *val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+	for (unsigned int i = 0; i < 2; i++) {
+		TEST(fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0));
+		if (TST_RET == -1)
+			tst_brk(TFAIL | TTERRNO, "fsconfig(FSCONFIG_SET_STRING) failed");
+	}
+	tst_res(TPASS, "Try fsconfig overflow on %s done!", tst_device->fs_type);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.format_device = 1,
+	.mntpoint = MNTPOINT,
+	.all_filesystems = 1,
+	.skip_filesystems = (const char *const []){"fuse", "ext2", "xfs", "tmpfs", NULL},
+};