Message ID | 1527346246-1334-1-git-send-email-s.mesoraca16@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Sat, May 26, 2018 at 04:50:46PM +0200, Salvatore Mesoraca wrote: > Prevent a task from opening, in "write" mode, any /proc/*/mem > file that operates on the task's mm. > /proc/*/mem is mainly a debugging means and, as such, it shouldn't > be used by the inspected process itself. > Current implementation always allow a task to access its own > /proc/*/mem file. > A process can use it to overwrite read-only memory, making > pointless the use of security_file_mprotect() or other ways to > enforce RO memory. You can do it in security_ptrace_access_check() or security_file_open()
2018-05-26 17:48 GMT+02:00 Alexey Dobriyan <adobriyan@gmail.com>: > On Sat, May 26, 2018 at 04:50:46PM +0200, Salvatore Mesoraca wrote: >> Prevent a task from opening, in "write" mode, any /proc/*/mem >> file that operates on the task's mm. >> /proc/*/mem is mainly a debugging means and, as such, it shouldn't >> be used by the inspected process itself. >> Current implementation always allow a task to access its own >> /proc/*/mem file. >> A process can use it to overwrite read-only memory, making >> pointless the use of security_file_mprotect() or other ways to >> enforce RO memory. > > You can do it in security_ptrace_access_check() No, because that hook is skipped when mm == current->mm: https://elixir.bootlin.com/linux/v4.17-rc6/source/kernel/fork.c#L1111 > or security_file_open() This is true, but it looks a bit overkill to me, especially since many of the macros/functions used to handle proc's files won't be in scope for an external LSM. Is there any particular reason why you prefer it done via LSM? Thank you, Salvatore
On 5/26/2018 10:30 AM, Salvatore Mesoraca wrote: > 2018-05-26 17:48 GMT+02:00 Alexey Dobriyan <adobriyan@gmail.com>: >> On Sat, May 26, 2018 at 04:50:46PM +0200, Salvatore Mesoraca wrote: >>> Prevent a task from opening, in "write" mode, any /proc/*/mem >>> file that operates on the task's mm. >>> /proc/*/mem is mainly a debugging means and, as such, it shouldn't >>> be used by the inspected process itself. >>> Current implementation always allow a task to access its own >>> /proc/*/mem file. >>> A process can use it to overwrite read-only memory, making >>> pointless the use of security_file_mprotect() or other ways to >>> enforce RO memory. >> You can do it in security_ptrace_access_check() > No, because that hook is skipped when mm == current->mm: > https://elixir.bootlin.com/linux/v4.17-rc6/source/kernel/fork.c#L1111 > >> or security_file_open() > This is true, but it looks a bit overkill to me, especially since many of > the macros/functions used to handle proc's files won't be in scope > for an external LSM. > Is there any particular reason why you prefer it done via LSM? If you did a Yama style LSM it would be easy to configure. Even though it might make no sense to allow this behavior, someone, somewhere is counting on it. > > Thank you, > > Salvatore > -- > To unsubscribe from this list: send the line "unsubscribe linux-security-module" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html >
On Sat, May 26, 2018 at 07:30:47PM +0200, Salvatore Mesoraca wrote: > 2018-05-26 17:48 GMT+02:00 Alexey Dobriyan <adobriyan@gmail.com>: > > On Sat, May 26, 2018 at 04:50:46PM +0200, Salvatore Mesoraca wrote: > >> Prevent a task from opening, in "write" mode, any /proc/*/mem > >> file that operates on the task's mm. > >> /proc/*/mem is mainly a debugging means and, as such, it shouldn't > >> be used by the inspected process itself. > >> Current implementation always allow a task to access its own > >> /proc/*/mem file. > >> A process can use it to overwrite read-only memory, making > >> pointless the use of security_file_mprotect() or other ways to > >> enforce RO memory. > > > > You can do it in security_ptrace_access_check() > > No, because that hook is skipped when mm == current->mm: > https://elixir.bootlin.com/linux/v4.17-rc6/source/kernel/fork.c#L1111 OK > > or security_file_open() > > This is true, but it looks a bit overkill to me, especially since many of > the macros/functions used to handle proc's files won't be in scope > for an external LSM. > Is there any particular reason why you prefer it done via LSM? Well, it exists to implement all kinds of non-standard restrictions. You're probably blacklisting mprotect() and worry that compromised program might use /proc/self/mem instead. But you need to blacklist much more that mprotect(). I think forking a dummy "worker" process to open your /proc/*/mem and pass a descriptor back should still work with your patch.
On Sat, May 26, 2018 at 7:50 AM, Salvatore Mesoraca <s.mesoraca16@gmail.com> wrote: > Prevent a task from opening, in "write" mode, any /proc/*/mem > file that operates on the task's mm. > /proc/*/mem is mainly a debugging means and, as such, it shouldn't > be used by the inspected process itself. > Current implementation always allow a task to access its own > /proc/*/mem file. > A process can use it to overwrite read-only memory, making > pointless the use of security_file_mprotect() or other ways to > enforce RO memory. I went through some old threads from 2012 when e268337dfe26 was introduced, and later when things got looked at during DirtyCOW. There was discussion about removing FOLL_FORCE (in order to block writes on a read-only memory region). But that was much more general, touched ptrace, etc. I think this patch would be okay, since it's specific to the proc "self" mem interface, not remote processes (via ptrace). This patch would also have blocked the /proc/self/mem path to DirtyCOW (though not ptrace), so that would be nice if we have similar issues in the future. So, as long as this doesn't break anything, I'm for it in general. I've CCed Linus and Jann too, since they've stared at this a lot too. :P Note that you're re-checking the mm-check-for-self in mm_access(). That's used in /proc and for process_vm_write(). Ptrace (and mm_access()) uses ptrace_may_access() for stuff (which has a similar check to bypass LSMs), so I'd be curious what would happen if this logic was plumbed into mm_access() instead of into proc_mem_open(). (Does anything open /proc/$pid files for writing? Does anything using process_vm_write() on itself?) -Kees > > Signed-off-by: Salvatore Mesoraca <s.mesoraca16@gmail.com> > --- > fs/proc/base.c | 25 ++++++++++++++++++------- > fs/proc/internal.h | 3 ++- > fs/proc/task_mmu.c | 4 ++-- > fs/proc/task_nommu.c | 2 +- > 4 files changed, 23 insertions(+), 11 deletions(-) > > diff --git a/fs/proc/base.c b/fs/proc/base.c > index 1a76d75..01ecfec 100644 > --- a/fs/proc/base.c > +++ b/fs/proc/base.c > @@ -762,8 +762,9 @@ static int proc_single_open(struct inode *inode, struct file *filp) > .release = single_release, > }; > > - > -struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) > +struct mm_struct *proc_mem_open(struct inode *inode, > + unsigned int mode, > + fmode_t f_mode) > { > struct task_struct *task = get_proc_task(inode); > struct mm_struct *mm = ERR_PTR(-ESRCH); > @@ -773,10 +774,20 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) > put_task_struct(task); > > if (!IS_ERR_OR_NULL(mm)) { > - /* ensure this mm_struct can't be freed */ > - mmgrab(mm); > - /* but do not pin its memory */ > - mmput(mm); > + /* > + * Prevent this interface from being used as a mean > + * to bypass memory restrictions, including those > + * imposed by LSMs. > + */ > + if (mm == current->mm && > + f_mode & FMODE_WRITE) > + mm = ERR_PTR(-EACCES); > + else { > + /* ensure this mm_struct can't be freed */ > + mmgrab(mm); > + /* but do not pin its memory */ > + mmput(mm); > + } > } > } > > @@ -785,7 +796,7 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) > > static int __mem_open(struct inode *inode, struct file *file, unsigned int mode) > { > - struct mm_struct *mm = proc_mem_open(inode, mode); > + struct mm_struct *mm = proc_mem_open(inode, mode, file->f_mode); > > if (IS_ERR(mm)) > return PTR_ERR(mm); > diff --git a/fs/proc/internal.h b/fs/proc/internal.h > index 0f1692e..8d38cc7 100644 > --- a/fs/proc/internal.h > +++ b/fs/proc/internal.h > @@ -275,7 +275,8 @@ struct proc_maps_private { > #endif > } __randomize_layout; > > -struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); > +struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode, > + fmode_t f_mode); > > extern const struct file_operations proc_pid_maps_operations; > extern const struct file_operations proc_tid_maps_operations; > diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c > index c486ad4..efb6535 100644 > --- a/fs/proc/task_mmu.c > +++ b/fs/proc/task_mmu.c > @@ -227,7 +227,7 @@ static int proc_maps_open(struct inode *inode, struct file *file, > return -ENOMEM; > > priv->inode = inode; > - priv->mm = proc_mem_open(inode, PTRACE_MODE_READ); > + priv->mm = proc_mem_open(inode, PTRACE_MODE_READ, file->f_mode); > if (IS_ERR(priv->mm)) { > int err = PTR_ERR(priv->mm); > > @@ -1534,7 +1534,7 @@ static int pagemap_open(struct inode *inode, struct file *file) > { > struct mm_struct *mm; > > - mm = proc_mem_open(inode, PTRACE_MODE_READ); > + mm = proc_mem_open(inode, PTRACE_MODE_READ, file->f_mode); > if (IS_ERR(mm)) > return PTR_ERR(mm); > file->private_data = mm; > diff --git a/fs/proc/task_nommu.c b/fs/proc/task_nommu.c > index 5b62f57..dc38516 100644 > --- a/fs/proc/task_nommu.c > +++ b/fs/proc/task_nommu.c > @@ -280,7 +280,7 @@ static int maps_open(struct inode *inode, struct file *file, > return -ENOMEM; > > priv->inode = inode; > - priv->mm = proc_mem_open(inode, PTRACE_MODE_READ); > + priv->mm = proc_mem_open(inode, PTRACE_MODE_READ, file->f_mode); > if (IS_ERR(priv->mm)) { > int err = PTR_ERR(priv->mm); > > -- > 1.9.1 >
On Sat, May 26, 2018 at 5:32 PM Kees Cook <keescook@chromium.org> wrote: > I went through some old threads from 2012 when e268337dfe26 was > introduced, and later when things got looked at during DirtyCOW. There > was discussion about removing FOLL_FORCE (in order to block writes on > a read-only memory region). Side note, we did that for /dev/mem, and things broke. Thus commit f511c0b17b08 "Yes, people use FOLL_FORCE ;)" Side note, that very sam ecommit f511c0b17b08 is also the explanation for why the patch under discussion now seems broken. People really do use "write to /proc/self/mem" as a way to keep the mappings read-only, but have a way to change them when required. Linus
On Sat, May 26, 2018 at 6:33 PM, Linus Torvalds <torvalds@linux-foundation.org> wrote: > Thus commit f511c0b17b08 "Yes, people use FOLL_FORCE ;)" > > Side note, that very sam ecommit f511c0b17b08 is also the explanation for > why the patch under discussion now seems broken. > > People really do use "write to /proc/self/mem" as a way to keep the > mappings read-only, but have a way to change them when required. Ah! Yes, that is the commit I was trying to find. Thanks! -Kees
On Sat, May 26, 2018 at 4:50 PM, Salvatore Mesoraca <s.mesoraca16@gmail.com> wrote: > Prevent a task from opening, in "write" mode, any /proc/*/mem > file that operates on the task's mm. > /proc/*/mem is mainly a debugging means and, as such, it shouldn't > be used by the inspected process itself. > Current implementation always allow a task to access its own > /proc/*/mem file. > A process can use it to overwrite read-only memory, making > pointless the use of security_file_mprotect() or other ways to > enforce RO memory. > > Signed-off-by: Salvatore Mesoraca <s.mesoraca16@gmail.com> > --- > fs/proc/base.c | 25 ++++++++++++++++++------- > fs/proc/internal.h | 3 ++- > fs/proc/task_mmu.c | 4 ++-- > fs/proc/task_nommu.c | 2 +- > 4 files changed, 23 insertions(+), 11 deletions(-) > > diff --git a/fs/proc/base.c b/fs/proc/base.c > index 1a76d75..01ecfec 100644 > --- a/fs/proc/base.c > +++ b/fs/proc/base.c > @@ -762,8 +762,9 @@ static int proc_single_open(struct inode *inode, struct file *filp) > .release = single_release, > }; > > - > -struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) > +struct mm_struct *proc_mem_open(struct inode *inode, > + unsigned int mode, > + fmode_t f_mode) > { > struct task_struct *task = get_proc_task(inode); > struct mm_struct *mm = ERR_PTR(-ESRCH); > @@ -773,10 +774,20 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) > put_task_struct(task); > > if (!IS_ERR_OR_NULL(mm)) { > - /* ensure this mm_struct can't be freed */ > - mmgrab(mm); > - /* but do not pin its memory */ > - mmput(mm); > + /* > + * Prevent this interface from being used as a mean > + * to bypass memory restrictions, including those > + * imposed by LSMs. > + */ > + if (mm == current->mm && > + f_mode & FMODE_WRITE) > + mm = ERR_PTR(-EACCES); > + else { > + /* ensure this mm_struct can't be freed */ > + mmgrab(mm); > + /* but do not pin its memory */ > + mmput(mm); > + } > } > } I don't have an opinion on the overall patch, but this part looks buggy: In the error path, you set `mm` to an error pointer, but you still own the reference that mm_access() took on the old `mm`. The error path needs to call `mmput(mm)`.
2018-05-27 3:33 GMT+02:00 Linus Torvalds <torvalds@linux-foundation.org>: > On Sat, May 26, 2018 at 5:32 PM Kees Cook <keescook@chromium.org> wrote: > >> I went through some old threads from 2012 when e268337dfe26 was >> introduced, and later when things got looked at during DirtyCOW. There >> was discussion about removing FOLL_FORCE (in order to block writes on >> a read-only memory region). > > Side note, we did that for /dev/mem, and things broke. > > Thus commit f511c0b17b08 "Yes, people use FOLL_FORCE ;)" > > Side note, that very sam ecommit f511c0b17b08 is also the explanation for > why the patch under discussion now seems broken. > > People really do use "write to /proc/self/mem" as a way to keep the > mappings read-only, but have a way to change them when required. Oh, I didn't expect this, interesting... A configurable LSM is probably the right way to do this. Thank you for your time, Salvatore
2018-05-28 11:06 GMT+02:00 Jann Horn <jannh@google.com>: > On Sat, May 26, 2018 at 4:50 PM, Salvatore Mesoraca > <s.mesoraca16@gmail.com> wrote: >> Prevent a task from opening, in "write" mode, any /proc/*/mem >> file that operates on the task's mm. >> /proc/*/mem is mainly a debugging means and, as such, it shouldn't >> be used by the inspected process itself. >> Current implementation always allow a task to access its own >> /proc/*/mem file. >> A process can use it to overwrite read-only memory, making >> pointless the use of security_file_mprotect() or other ways to >> enforce RO memory. >> >> Signed-off-by: Salvatore Mesoraca <s.mesoraca16@gmail.com> >> --- >> fs/proc/base.c | 25 ++++++++++++++++++------- >> fs/proc/internal.h | 3 ++- >> fs/proc/task_mmu.c | 4 ++-- >> fs/proc/task_nommu.c | 2 +- >> 4 files changed, 23 insertions(+), 11 deletions(-) >> >> diff --git a/fs/proc/base.c b/fs/proc/base.c >> index 1a76d75..01ecfec 100644 >> --- a/fs/proc/base.c >> +++ b/fs/proc/base.c >> @@ -762,8 +762,9 @@ static int proc_single_open(struct inode *inode, struct file *filp) >> .release = single_release, >> }; >> >> - >> -struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) >> +struct mm_struct *proc_mem_open(struct inode *inode, >> + unsigned int mode, >> + fmode_t f_mode) >> { >> struct task_struct *task = get_proc_task(inode); >> struct mm_struct *mm = ERR_PTR(-ESRCH); >> @@ -773,10 +774,20 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) >> put_task_struct(task); >> >> if (!IS_ERR_OR_NULL(mm)) { >> - /* ensure this mm_struct can't be freed */ >> - mmgrab(mm); >> - /* but do not pin its memory */ >> - mmput(mm); >> + /* >> + * Prevent this interface from being used as a mean >> + * to bypass memory restrictions, including those >> + * imposed by LSMs. >> + */ >> + if (mm == current->mm && >> + f_mode & FMODE_WRITE) >> + mm = ERR_PTR(-EACCES); >> + else { >> + /* ensure this mm_struct can't be freed */ >> + mmgrab(mm); >> + /* but do not pin its memory */ >> + mmput(mm); >> + } >> } >> } > > I don't have an opinion on the overall patch, but this part looks > buggy: In the error path, you set `mm` to an error pointer, but you > still own the reference that mm_access() took on the old `mm`. The > error path needs to call `mmput(mm)`. You are absolutely right, Thank you, Salvatore
diff --git a/fs/proc/base.c b/fs/proc/base.c index 1a76d75..01ecfec 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -762,8 +762,9 @@ static int proc_single_open(struct inode *inode, struct file *filp) .release = single_release, }; - -struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) +struct mm_struct *proc_mem_open(struct inode *inode, + unsigned int mode, + fmode_t f_mode) { struct task_struct *task = get_proc_task(inode); struct mm_struct *mm = ERR_PTR(-ESRCH); @@ -773,10 +774,20 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) put_task_struct(task); if (!IS_ERR_OR_NULL(mm)) { - /* ensure this mm_struct can't be freed */ - mmgrab(mm); - /* but do not pin its memory */ - mmput(mm); + /* + * Prevent this interface from being used as a mean + * to bypass memory restrictions, including those + * imposed by LSMs. + */ + if (mm == current->mm && + f_mode & FMODE_WRITE) + mm = ERR_PTR(-EACCES); + else { + /* ensure this mm_struct can't be freed */ + mmgrab(mm); + /* but do not pin its memory */ + mmput(mm); + } } } @@ -785,7 +796,7 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) static int __mem_open(struct inode *inode, struct file *file, unsigned int mode) { - struct mm_struct *mm = proc_mem_open(inode, mode); + struct mm_struct *mm = proc_mem_open(inode, mode, file->f_mode); if (IS_ERR(mm)) return PTR_ERR(mm); diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 0f1692e..8d38cc7 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -275,7 +275,8 @@ struct proc_maps_private { #endif } __randomize_layout; -struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); +struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode, + fmode_t f_mode); extern const struct file_operations proc_pid_maps_operations; extern const struct file_operations proc_tid_maps_operations; diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index c486ad4..efb6535 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -227,7 +227,7 @@ static int proc_maps_open(struct inode *inode, struct file *file, return -ENOMEM; priv->inode = inode; - priv->mm = proc_mem_open(inode, PTRACE_MODE_READ); + priv->mm = proc_mem_open(inode, PTRACE_MODE_READ, file->f_mode); if (IS_ERR(priv->mm)) { int err = PTR_ERR(priv->mm); @@ -1534,7 +1534,7 @@ static int pagemap_open(struct inode *inode, struct file *file) { struct mm_struct *mm; - mm = proc_mem_open(inode, PTRACE_MODE_READ); + mm = proc_mem_open(inode, PTRACE_MODE_READ, file->f_mode); if (IS_ERR(mm)) return PTR_ERR(mm); file->private_data = mm; diff --git a/fs/proc/task_nommu.c b/fs/proc/task_nommu.c index 5b62f57..dc38516 100644 --- a/fs/proc/task_nommu.c +++ b/fs/proc/task_nommu.c @@ -280,7 +280,7 @@ static int maps_open(struct inode *inode, struct file *file, return -ENOMEM; priv->inode = inode; - priv->mm = proc_mem_open(inode, PTRACE_MODE_READ); + priv->mm = proc_mem_open(inode, PTRACE_MODE_READ, file->f_mode); if (IS_ERR(priv->mm)) { int err = PTR_ERR(priv->mm);
Prevent a task from opening, in "write" mode, any /proc/*/mem file that operates on the task's mm. /proc/*/mem is mainly a debugging means and, as such, it shouldn't be used by the inspected process itself. Current implementation always allow a task to access its own /proc/*/mem file. A process can use it to overwrite read-only memory, making pointless the use of security_file_mprotect() or other ways to enforce RO memory. Signed-off-by: Salvatore Mesoraca <s.mesoraca16@gmail.com> --- fs/proc/base.c | 25 ++++++++++++++++++------- fs/proc/internal.h | 3 ++- fs/proc/task_mmu.c | 4 ++-- fs/proc/task_nommu.c | 2 +- 4 files changed, 23 insertions(+), 11 deletions(-)