From: Sasha Levin Date: Mon, 1 Jul 2024 00:09:12 +0000 (-0400) Subject: Fixes for 6.1 X-Git-Tag: v4.19.317~116 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=af75a15ea763e801758781403cb68ef1d2f77805;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for 6.1 Signed-off-by: Sasha Levin --- diff --git a/queue-6.1/ima-fix-use-after-free-on-a-dentry-s-dname.name.patch b/queue-6.1/ima-fix-use-after-free-on-a-dentry-s-dname.name.patch new file mode 100644 index 00000000000..9d4e3fb4470 --- /dev/null +++ b/queue-6.1/ima-fix-use-after-free-on-a-dentry-s-dname.name.patch @@ -0,0 +1,121 @@ +From 009727e6d42137a33cde7f38c811281fb576b80f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 22 Mar 2024 10:03:12 -0400 +Subject: ima: Fix use-after-free on a dentry's dname.name + +From: Stefan Berger + +[ Upstream commit be84f32bb2c981ca670922e047cdde1488b233de ] + +->d_name.name can change on rename and the earlier value can be freed; +there are conditions sufficient to stabilize it (->d_lock on dentry, +->d_lock on its parent, ->i_rwsem exclusive on the parent's inode, +rename_lock), but none of those are met at any of the sites. Take a stable +snapshot of the name instead. + +Link: https://lore.kernel.org/all/20240202182732.GE2087318@ZenIV/ +Signed-off-by: Al Viro +Signed-off-by: Stefan Berger +Signed-off-by: Mimi Zohar +Signed-off-by: Sasha Levin +--- + security/integrity/ima/ima_api.c | 16 ++++++++++++---- + security/integrity/ima/ima_template_lib.c | 17 ++++++++++++++--- + 2 files changed, 26 insertions(+), 7 deletions(-) + +diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c +index 7a244e8ce65a5..cea8df0115a28 100644 +--- a/security/integrity/ima/ima_api.c ++++ b/security/integrity/ima/ima_api.c +@@ -243,8 +243,8 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, + const char *audit_cause = "failed"; + struct inode *inode = file_inode(file); + struct inode *real_inode = d_real_inode(file_dentry(file)); +- const char *filename = file->f_path.dentry->d_name.name; + struct ima_max_digest_data hash; ++ struct name_snapshot filename; + struct kstat stat; + int result = 0; + int length; +@@ -322,9 +322,13 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, + if (file->f_flags & O_DIRECT) + audit_cause = "failed(directio)"; + ++ take_dentry_name_snapshot(&filename, file->f_path.dentry); ++ + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, +- filename, "collect_data", audit_cause, +- result, 0); ++ filename.name.name, "collect_data", ++ audit_cause, result, 0); ++ ++ release_dentry_name_snapshot(&filename); + } + return result; + } +@@ -437,6 +441,7 @@ void ima_audit_measurement(struct integrity_iint_cache *iint, + */ + const char *ima_d_path(const struct path *path, char **pathbuf, char *namebuf) + { ++ struct name_snapshot filename; + char *pathname = NULL; + + *pathbuf = __getname(); +@@ -450,7 +455,10 @@ const char *ima_d_path(const struct path *path, char **pathbuf, char *namebuf) + } + + if (!pathname) { +- strscpy(namebuf, path->dentry->d_name.name, NAME_MAX); ++ take_dentry_name_snapshot(&filename, path->dentry); ++ strscpy(namebuf, filename.name.name, NAME_MAX); ++ release_dentry_name_snapshot(&filename); ++ + pathname = namebuf; + } + +diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c +index 7bf9b15072202..41ec31debe870 100644 +--- a/security/integrity/ima/ima_template_lib.c ++++ b/security/integrity/ima/ima_template_lib.c +@@ -483,7 +483,10 @@ static int ima_eventname_init_common(struct ima_event_data *event_data, + bool size_limit) + { + const char *cur_filename = NULL; ++ struct name_snapshot filename; + u32 cur_filename_len = 0; ++ bool snapshot = false; ++ int ret; + + BUG_ON(event_data->filename == NULL && event_data->file == NULL); + +@@ -496,7 +499,10 @@ static int ima_eventname_init_common(struct ima_event_data *event_data, + } + + if (event_data->file) { +- cur_filename = event_data->file->f_path.dentry->d_name.name; ++ take_dentry_name_snapshot(&filename, ++ event_data->file->f_path.dentry); ++ snapshot = true; ++ cur_filename = filename.name.name; + cur_filename_len = strlen(cur_filename); + } else + /* +@@ -505,8 +511,13 @@ static int ima_eventname_init_common(struct ima_event_data *event_data, + */ + cur_filename_len = IMA_EVENT_NAME_LEN_MAX; + out: +- return ima_write_template_field_data(cur_filename, cur_filename_len, +- DATA_FMT_STRING, field_data); ++ ret = ima_write_template_field_data(cur_filename, cur_filename_len, ++ DATA_FMT_STRING, field_data); ++ ++ if (snapshot) ++ release_dentry_name_snapshot(&filename); ++ ++ return ret; + } + + /* +-- +2.43.0 + diff --git a/queue-6.1/ima-use-vfs_getattr_nosec-to-get-the-i_version.patch b/queue-6.1/ima-use-vfs_getattr_nosec-to-get-the-i_version.patch new file mode 100644 index 00000000000..6ab879cf8ef --- /dev/null +++ b/queue-6.1/ima-use-vfs_getattr_nosec-to-get-the-i_version.patch @@ -0,0 +1,101 @@ +From 1ecfaeacb62b0e0c7cbe178410b6bd5dc2a394b1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 17 Apr 2023 12:55:51 -0400 +Subject: IMA: use vfs_getattr_nosec to get the i_version + +From: Jeff Layton + +[ Upstream commit db1d1e8b9867aae5c3e61ad7859abfcc4a6fd6c7 ] + +IMA currently accesses the i_version out of the inode directly when it +does a measurement. This is fine for most simple filesystems, but can be +problematic with more complex setups (e.g. overlayfs). + +Make IMA instead call vfs_getattr_nosec to get this info. This allows +the filesystem to determine whether and how to report the i_version, and +should allow IMA to work properly with a broader class of filesystems in +the future. + +Reported-and-Tested-by: Stefan Berger +Reviewed-by: Christian Brauner +Signed-off-by: Jeff Layton +Signed-off-by: Mimi Zohar +Stable-dep-of: be84f32bb2c9 ("ima: Fix use-after-free on a dentry's dname.name") +Signed-off-by: Sasha Levin +--- + security/integrity/ima/ima_api.c | 9 ++++++--- + security/integrity/ima/ima_main.c | 12 ++++++++---- + 2 files changed, 14 insertions(+), 7 deletions(-) + +diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c +index 026c8c9db9920..7a244e8ce65a5 100644 +--- a/security/integrity/ima/ima_api.c ++++ b/security/integrity/ima/ima_api.c +@@ -13,7 +13,6 @@ + #include + #include + #include +-#include + #include + + #include "ima.h" +@@ -246,10 +245,11 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, + struct inode *real_inode = d_real_inode(file_dentry(file)); + const char *filename = file->f_path.dentry->d_name.name; + struct ima_max_digest_data hash; ++ struct kstat stat; + int result = 0; + int length; + void *tmpbuf; +- u64 i_version; ++ u64 i_version = 0; + + /* + * Always collect the modsig, because IMA might have already collected +@@ -268,7 +268,10 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, + * to an initial measurement/appraisal/audit, but was modified to + * assume the file changed. + */ +- i_version = inode_query_iversion(inode); ++ result = vfs_getattr_nosec(&file->f_path, &stat, STATX_CHANGE_COOKIE, ++ AT_STATX_SYNC_AS_STAT); ++ if (!result && (stat.result_mask & STATX_CHANGE_COOKIE)) ++ i_version = stat.change_cookie; + hash.hdr.algo = algo; + hash.hdr.length = hash_digest_size[algo]; + +diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c +index 185666d90eebc..bba421f617312 100644 +--- a/security/integrity/ima/ima_main.c ++++ b/security/integrity/ima/ima_main.c +@@ -24,7 +24,6 @@ + #include + #include + #include +-#include + #include + #include + +@@ -164,11 +163,16 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, + + mutex_lock(&iint->mutex); + if (atomic_read(&inode->i_writecount) == 1) { ++ struct kstat stat; ++ + update = test_and_clear_bit(IMA_UPDATE_XATTR, + &iint->atomic_flags); +- if (!IS_I_VERSION(inode) || +- !inode_eq_iversion(inode, iint->version) || +- (iint->flags & IMA_NEW_FILE)) { ++ if ((iint->flags & IMA_NEW_FILE) || ++ vfs_getattr_nosec(&file->f_path, &stat, ++ STATX_CHANGE_COOKIE, ++ AT_STATX_SYNC_AS_STAT) || ++ !(stat.result_mask & STATX_CHANGE_COOKIE) || ++ stat.change_cookie != iint->version) { + iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE); + iint->measured_pcrs = 0; + if (update) +-- +2.43.0 + diff --git a/queue-6.1/parisc-use-generic-sys_fanotify_mark-implementation.patch b/queue-6.1/parisc-use-generic-sys_fanotify_mark-implementation.patch new file mode 100644 index 00000000000..1219f6fb94d --- /dev/null +++ b/queue-6.1/parisc-use-generic-sys_fanotify_mark-implementation.patch @@ -0,0 +1,99 @@ +From dddb1721bbdc9c1c3a77aa91200ff51db17ed729 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 7 Jun 2024 13:40:45 +0200 +Subject: parisc: use generic sys_fanotify_mark implementation + +From: Arnd Bergmann + +[ Upstream commit 403f17a330732a666ae793f3b15bc75bb5540524 ] + +The sys_fanotify_mark() syscall on parisc uses the reverse word order +for the two halves of the 64-bit argument compared to all syscalls on +all 32-bit architectures. As far as I can tell, the problem is that +the function arguments on parisc are sorted backwards (26, 25, 24, 23, +...) compared to everyone else, so the calling conventions of using an +even/odd register pair in native word order result in the lower word +coming first in function arguments, matching the expected behavior +on little-endian architectures. The system call conventions however +ended up matching what the other 32-bit architectures do. + +A glibc cleanup in 2020 changed the userspace behavior in a way that +handles all architectures consistently, but this inadvertently broke +parisc32 by changing to the same method as everyone else. + +The change made it into glibc-2.35 and subsequently into debian 12 +(bookworm), which is the latest stable release. This means we +need to choose between reverting the glibc change or changing the +kernel to match it again, but either hange will leave some systems +broken. + +Pick the option that is more likely to help current and future +users and change the kernel to match current glibc. This also +means the behavior is now consistent across architectures, but +it breaks running new kernels with old glibc builds before 2.35. + +Link: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d150181d73d9 +Link: https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git/commit/arch/parisc/kernel/sys_parisc.c?h=57b1dfbd5b4a39d +Cc: Adhemerval Zanella +Tested-by: Helge Deller +Acked-by: Helge Deller +Signed-off-by: Arnd Bergmann +--- +I found this through code inspection, please double-check to make +sure I got the bug and the fix right. + +The alternative is to fix this by reverting glibc back to the +unusual behavior. + +Signed-off-by: Sasha Levin +--- + arch/parisc/Kconfig | 1 + + arch/parisc/kernel/sys_parisc32.c | 9 --------- + arch/parisc/kernel/syscalls/syscall.tbl | 2 +- + 3 files changed, 2 insertions(+), 10 deletions(-) + +diff --git a/arch/parisc/Kconfig b/arch/parisc/Kconfig +index abf39ecda6fb1..5762633ea95e4 100644 +--- a/arch/parisc/Kconfig ++++ b/arch/parisc/Kconfig +@@ -14,6 +14,7 @@ config PARISC + select ARCH_HAS_UBSAN_SANITIZE_ALL + select ARCH_HAS_PTE_SPECIAL + select ARCH_NO_SG_CHAIN ++ select ARCH_SPLIT_ARG64 if !64BIT + select ARCH_SUPPORTS_HUGETLBFS if PA20 + select ARCH_SUPPORTS_MEMORY_FAILURE + select ARCH_STACKWALK +diff --git a/arch/parisc/kernel/sys_parisc32.c b/arch/parisc/kernel/sys_parisc32.c +index 2a12a547b447b..826c8e51b5853 100644 +--- a/arch/parisc/kernel/sys_parisc32.c ++++ b/arch/parisc/kernel/sys_parisc32.c +@@ -23,12 +23,3 @@ asmlinkage long sys32_unimplemented(int r26, int r25, int r24, int r23, + current->comm, current->pid, r20); + return -ENOSYS; + } +- +-asmlinkage long sys32_fanotify_mark(compat_int_t fanotify_fd, compat_uint_t flags, +- compat_uint_t mask0, compat_uint_t mask1, compat_int_t dfd, +- const char __user * pathname) +-{ +- return sys_fanotify_mark(fanotify_fd, flags, +- ((__u64)mask1 << 32) | mask0, +- dfd, pathname); +-} +diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl +index ba4884eaa5057..42702f5d49f28 100644 +--- a/arch/parisc/kernel/syscalls/syscall.tbl ++++ b/arch/parisc/kernel/syscalls/syscall.tbl +@@ -364,7 +364,7 @@ + 320 common accept4 sys_accept4 + 321 common prlimit64 sys_prlimit64 + 322 common fanotify_init sys_fanotify_init +-323 common fanotify_mark sys_fanotify_mark sys32_fanotify_mark ++323 common fanotify_mark sys_fanotify_mark compat_sys_fanotify_mark + 324 32 clock_adjtime sys_clock_adjtime32 + 324 64 clock_adjtime sys_clock_adjtime + 325 common name_to_handle_at sys_name_to_handle_at +-- +2.43.0 + diff --git a/queue-6.1/series b/queue-6.1/series index dc5d0a2632a..ac3392b9979 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -60,3 +60,8 @@ drm-amdgpu-fix-pci-state-save-during-mode-1-reset.patch riscv-stacktrace-convert-arch_stack_walk-to-noinstr.patch gpiolib-cdev-disallow-reconfiguration-without-direct.patch randomize_kstack-remove-non-functional-per-arch-entr.patch +vfs-plumb-i_version-handling-into-struct-kstat.patch +ima-use-vfs_getattr_nosec-to-get-the-i_version.patch +ima-fix-use-after-free-on-a-dentry-s-dname.name.patch +x86-stop-playing-stack-games-in-profile_pc.patch +parisc-use-generic-sys_fanotify_mark-implementation.patch diff --git a/queue-6.1/vfs-plumb-i_version-handling-into-struct-kstat.patch b/queue-6.1/vfs-plumb-i_version-handling-into-struct-kstat.patch new file mode 100644 index 00000000000..3a1cb038217 --- /dev/null +++ b/queue-6.1/vfs-plumb-i_version-handling-into-struct-kstat.patch @@ -0,0 +1,112 @@ +From 72aa39d07c4636965c9a8c7e965476f103d8a398 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Sun, 4 Dec 2016 09:29:46 -0500 +Subject: vfs: plumb i_version handling into struct kstat + +From: Jeff Layton + +[ Upstream commit a1175d6b1bdaf4f74eda47ab18eb44194f9cb796 ] + +The NFS server has a lot of special handling for different types of +change attribute access, depending on the underlying filesystem. In +most cases, it's doing a getattr anyway and then fetching that value +after the fact. + +Rather that do that, add a new STATX_CHANGE_COOKIE flag that is a +kernel-only symbol (for now). If requested and getattr can implement it, +it can fill out this field. For IS_I_VERSION inodes, add a generic +implementation in vfs_getattr_nosec. Take care to mask +STATX_CHANGE_COOKIE off in requests from userland and in the result +mask. + +Since not all filesystems can give the same guarantees of monotonicity, +claim a STATX_ATTR_CHANGE_MONOTONIC flag that filesystems can set to +indicate that they offer an i_version value that can never go backward. + +Eventually if we decide to make the i_version available to userland, we +can just designate a field for it in struct statx, and move the +STATX_CHANGE_COOKIE definition to the uapi header. + +Reviewed-by: NeilBrown +Reviewed-by: Jan Kara +Signed-off-by: Jeff Layton +Stable-dep-of: be84f32bb2c9 ("ima: Fix use-after-free on a dentry's dname.name") +Signed-off-by: Sasha Levin +--- + fs/stat.c | 17 +++++++++++++++-- + include/linux/stat.h | 9 +++++++++ + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/fs/stat.c b/fs/stat.c +index ef50573c72a26..06fd3fc1ab84b 100644 +--- a/fs/stat.c ++++ b/fs/stat.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -119,6 +120,11 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat, + stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT | + STATX_ATTR_DAX); + ++ if ((request_mask & STATX_CHANGE_COOKIE) && IS_I_VERSION(inode)) { ++ stat->result_mask |= STATX_CHANGE_COOKIE; ++ stat->change_cookie = inode_query_iversion(inode); ++ } ++ + mnt_userns = mnt_user_ns(path->mnt); + if (inode->i_op->getattr) + return inode->i_op->getattr(mnt_userns, path, stat, +@@ -599,9 +605,11 @@ cp_statx(const struct kstat *stat, struct statx __user *buffer) + + memset(&tmp, 0, sizeof(tmp)); + +- tmp.stx_mask = stat->result_mask; ++ /* STATX_CHANGE_COOKIE is kernel-only for now */ ++ tmp.stx_mask = stat->result_mask & ~STATX_CHANGE_COOKIE; + tmp.stx_blksize = stat->blksize; +- tmp.stx_attributes = stat->attributes; ++ /* STATX_ATTR_CHANGE_MONOTONIC is kernel-only for now */ ++ tmp.stx_attributes = stat->attributes & ~STATX_ATTR_CHANGE_MONOTONIC; + tmp.stx_nlink = stat->nlink; + tmp.stx_uid = from_kuid_munged(current_user_ns(), stat->uid); + tmp.stx_gid = from_kgid_munged(current_user_ns(), stat->gid); +@@ -640,6 +648,11 @@ int do_statx(int dfd, struct filename *filename, unsigned int flags, + if ((flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE) + return -EINVAL; + ++ /* STATX_CHANGE_COOKIE is kernel-only for now. Ignore requests ++ * from userland. ++ */ ++ mask &= ~STATX_CHANGE_COOKIE; ++ + error = vfs_statx(dfd, filename, flags, &stat, mask); + if (error) + return error; +diff --git a/include/linux/stat.h b/include/linux/stat.h +index ff277ced50e9f..52150570d37a5 100644 +--- a/include/linux/stat.h ++++ b/include/linux/stat.h +@@ -52,6 +52,15 @@ struct kstat { + u64 mnt_id; + u32 dio_mem_align; + u32 dio_offset_align; ++ u64 change_cookie; + }; + ++/* These definitions are internal to the kernel for now. Mainly used by nfsd. */ ++ ++/* mask values */ ++#define STATX_CHANGE_COOKIE 0x40000000U /* Want/got stx_change_attr */ ++ ++/* file attribute values */ ++#define STATX_ATTR_CHANGE_MONOTONIC 0x8000000000000000ULL /* version monotonically increases */ ++ + #endif +-- +2.43.0 + diff --git a/queue-6.1/x86-stop-playing-stack-games-in-profile_pc.patch b/queue-6.1/x86-stop-playing-stack-games-in-profile_pc.patch new file mode 100644 index 00000000000..34fc54a44c4 --- /dev/null +++ b/queue-6.1/x86-stop-playing-stack-games-in-profile_pc.patch @@ -0,0 +1,95 @@ +From fb6bdc56f4055594ee4da1fcf0ca9c0400fab475 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 28 Jun 2024 14:27:22 -0700 +Subject: x86: stop playing stack games in profile_pc() + +From: Linus Torvalds + +[ Upstream commit 093d9603b60093a9aaae942db56107f6432a5dca ] + +The 'profile_pc()' function is used for timer-based profiling, which +isn't really all that relevant any more to begin with, but it also ends +up making assumptions based on the stack layout that aren't necessarily +valid. + +Basically, the code tries to account the time spent in spinlocks to the +caller rather than the spinlock, and while I support that as a concept, +it's not worth the code complexity or the KASAN warnings when no serious +profiling is done using timers anyway these days. + +And the code really does depend on stack layout that is only true in the +simplest of cases. We've lost the comment at some point (I think when +the 32-bit and 64-bit code was unified), but it used to say: + + Assume the lock function has either no stack frame or a copy + of eflags from PUSHF. + +which explains why it just blindly loads a word or two straight off the +stack pointer and then takes a minimal look at the values to just check +if they might be eflags or the return pc: + + Eflags always has bits 22 and up cleared unlike kernel addresses + +but that basic stack layout assumption assumes that there isn't any lock +debugging etc going on that would complicate the code and cause a stack +frame. + +It causes KASAN unhappiness reported for years by syzkaller [1] and +others [2]. + +With no real practical reason for this any more, just remove the code. + +Just for historical interest, here's some background commits relating to +this code from 2006: + + 0cb91a229364 ("i386: Account spinlocks to the caller during profiling for !FP kernels") + 31679f38d886 ("Simplify profile_pc on x86-64") + +and a code unification from 2009: + + ef4512882dbe ("x86: time_32/64.c unify profile_pc") + +but the basics of this thing actually goes back to before the git tree. + +Link: https://syzkaller.appspot.com/bug?extid=84fe685c02cd112a2ac3 [1] +Link: https://lore.kernel.org/all/CAK55_s7Xyq=nh97=K=G1sxueOFrJDAvPOJAL4TPTCAYvmxO9_A@mail.gmail.com/ [2] +Signed-off-by: Linus Torvalds +Signed-off-by: Sasha Levin +--- + arch/x86/kernel/time.c | 20 +------------------- + 1 file changed, 1 insertion(+), 19 deletions(-) + +diff --git a/arch/x86/kernel/time.c b/arch/x86/kernel/time.c +index e42faa792c079..52e1f3f0b361c 100644 +--- a/arch/x86/kernel/time.c ++++ b/arch/x86/kernel/time.c +@@ -27,25 +27,7 @@ + + unsigned long profile_pc(struct pt_regs *regs) + { +- unsigned long pc = instruction_pointer(regs); +- +- if (!user_mode(regs) && in_lock_functions(pc)) { +-#ifdef CONFIG_FRAME_POINTER +- return *(unsigned long *)(regs->bp + sizeof(long)); +-#else +- unsigned long *sp = (unsigned long *)regs->sp; +- /* +- * Return address is either directly at stack pointer +- * or above a saved flags. Eflags has bits 22-31 zero, +- * kernel addresses don't. +- */ +- if (sp[0] >> 22) +- return sp[0]; +- if (sp[1] >> 22) +- return sp[1]; +-#endif +- } +- return pc; ++ return instruction_pointer(regs); + } + EXPORT_SYMBOL(profile_pc); + +-- +2.43.0 +