diff mbox

arm: Use kernel mm when updating section permissions

Message ID 1446685239-28522-1-git-send-email-labbott@fedoraproject.org (mailing list archive)
State New, archived
Headers show

Commit Message

Laura Abbott Nov. 5, 2015, 1 a.m. UTC
Currently, read only permissions are not being applied even
when CONFIG_DEBUG_RODATA is set. This is because section_update
uses current->mm for adjusting the page tables. current->mm
need not be equivalent to the kernel version. Use pgd_offset_k
to get the proper page directory for updating.

Signed-off-by: Laura Abbott <labbott@fedoraproject.org>
---
I found this while trying to convince myself of something.
Dumping the page table via debugfs and writing to kernel text were both
showing the lack of mappings. This was observed on QEMU. Maybe it's just a
QEMUism but if not it probably should go to stable.
---
 arch/arm/mm/init.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

Comments

Kees Cook Nov. 5, 2015, 1:06 a.m. UTC | #1
On Wed, Nov 4, 2015 at 5:00 PM, Laura Abbott <labbott@fedoraproject.org> wrote:
> Currently, read only permissions are not being applied even
> when CONFIG_DEBUG_RODATA is set. This is because section_update
> uses current->mm for adjusting the page tables. current->mm
> need not be equivalent to the kernel version. Use pgd_offset_k
> to get the proper page directory for updating.
>
> Signed-off-by: Laura Abbott <labbott@fedoraproject.org>
> ---
> I found this while trying to convince myself of something.
> Dumping the page table via debugfs and writing to kernel text were both
> showing the lack of mappings. This was observed on QEMU. Maybe it's just a
> QEMUism but if not it probably should go to stable.

Well that's weird! debugfs showed the actual permissions that lacked
RO? I wonder what changed. I tested this both with debugfs and lkdtm's
KERN_WRITE test when the patches originally landed.

-Kees

> ---
>  arch/arm/mm/init.c | 4 +---
>  1 file changed, 1 insertion(+), 3 deletions(-)
>
> diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c
> index 8a63b4c..4bb936a 100644
> --- a/arch/arm/mm/init.c
> +++ b/arch/arm/mm/init.c
> @@ -629,11 +629,9 @@ static struct section_perm ro_perms[] = {
>  static inline void section_update(unsigned long addr, pmdval_t mask,
>                                   pmdval_t prot)
>  {
> -       struct mm_struct *mm;
>         pmd_t *pmd;
>
> -       mm = current->active_mm;
> -       pmd = pmd_offset(pud_offset(pgd_offset(mm, addr), addr), addr);
> +       pmd = pmd_offset(pud_offset(pgd_offset_k(addr), addr), addr);
>
>  #ifdef CONFIG_ARM_LPAE
>         pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot);
> --
> 2.5.0
>
Kees Cook Nov. 5, 2015, 1:13 a.m. UTC | #2
On Wed, Nov 4, 2015 at 5:06 PM, Kees Cook <keescook@chromium.org> wrote:
> On Wed, Nov 4, 2015 at 5:00 PM, Laura Abbott <labbott@fedoraproject.org> wrote:
>> Currently, read only permissions are not being applied even
>> when CONFIG_DEBUG_RODATA is set. This is because section_update
>> uses current->mm for adjusting the page tables. current->mm
>> need not be equivalent to the kernel version. Use pgd_offset_k
>> to get the proper page directory for updating.
>>
>> Signed-off-by: Laura Abbott <labbott@fedoraproject.org>
>> ---
>> I found this while trying to convince myself of something.
>> Dumping the page table via debugfs and writing to kernel text were both
>> showing the lack of mappings. This was observed on QEMU. Maybe it's just a
>> QEMUism but if not it probably should go to stable.
>
> Well that's weird! debugfs showed the actual permissions that lacked
> RO? I wonder what changed. I tested this both with debugfs and lkdtm's
> KERN_WRITE test when the patches originally landed.

The comment will need adjusting too. I have a memory of needing to use
current->mm to deal with some crazy errata and handling TLB flushes...

-Kees
Russell King - ARM Linux Nov. 5, 2015, 9:46 a.m. UTC | #3
On Wed, Nov 04, 2015 at 05:00:39PM -0800, Laura Abbott wrote:
> Currently, read only permissions are not being applied even
> when CONFIG_DEBUG_RODATA is set. This is because section_update
> uses current->mm for adjusting the page tables. current->mm
> need not be equivalent to the kernel version. Use pgd_offset_k
> to get the proper page directory for updating.

What are you trying to achieve here?  You can't use these functions
at run time (after the first thread has been spawned) to change
permissions, because there will be multiple copies of the kernel
section mappings, and those copies will not get updated.

In any case, this change will probably break kexec and ftrace, as
the running thread will no longer see the updated page tables.
Laura Abbott Nov. 5, 2015, 4:20 p.m. UTC | #4
On 11/05/2015 01:46 AM, Russell King - ARM Linux wrote:
> On Wed, Nov 04, 2015 at 05:00:39PM -0800, Laura Abbott wrote:
>> Currently, read only permissions are not being applied even
>> when CONFIG_DEBUG_RODATA is set. This is because section_update
>> uses current->mm for adjusting the page tables. current->mm
>> need not be equivalent to the kernel version. Use pgd_offset_k
>> to get the proper page directory for updating.
>
> What are you trying to achieve here?  You can't use these functions
> at run time (after the first thread has been spawned) to change
> permissions, because there will be multiple copies of the kernel
> section mappings, and those copies will not get updated.
>
> In any case, this change will probably break kexec and ftrace, as
> the running thread will no longer see the updated page tables.
>

I think I was hitting that exact problem with multiple copies
not getting updated. The section_update code was being called
and I was seeing the tables get updated but nothing was being
applied when I tried to write to text or check the debugfs
page table. The current flow is:

rest_init -> kernel_thread(kernel_init) and from that thread
mark_rodata_ro. So mark_rodata_ro is always going to happen
in a thread.

Do we need to update for both init_mm and the first running
thread?

Thanks,
Laura
Russell King - ARM Linux Nov. 5, 2015, 4:27 p.m. UTC | #5
On Thu, Nov 05, 2015 at 08:20:42AM -0800, Laura Abbott wrote:
> On 11/05/2015 01:46 AM, Russell King - ARM Linux wrote:
> >On Wed, Nov 04, 2015 at 05:00:39PM -0800, Laura Abbott wrote:
> >>Currently, read only permissions are not being applied even
> >>when CONFIG_DEBUG_RODATA is set. This is because section_update
> >>uses current->mm for adjusting the page tables. current->mm
> >>need not be equivalent to the kernel version. Use pgd_offset_k
> >>to get the proper page directory for updating.
> >
> >What are you trying to achieve here?  You can't use these functions
> >at run time (after the first thread has been spawned) to change
> >permissions, because there will be multiple copies of the kernel
> >section mappings, and those copies will not get updated.
> >
> >In any case, this change will probably break kexec and ftrace, as
> >the running thread will no longer see the updated page tables.
> >
> 
> I think I was hitting that exact problem with multiple copies
> not getting updated. The section_update code was being called
> and I was seeing the tables get updated but nothing was being
> applied when I tried to write to text or check the debugfs
> page table. The current flow is:
> 
> rest_init -> kernel_thread(kernel_init) and from that thread
> mark_rodata_ro. So mark_rodata_ro is always going to happen
> in a thread.
> 
> Do we need to update for both init_mm and the first running
> thread?

The "first running thread" is merely coincidental for things like kexec.

Hmm.  Actually, I think the existing code _should_ be fine.  At the
point where mark_rodata_ro() is, we should still be using init_mm, so
updating the current threads page tables should actually be updating
the swapper_pg_dir.

The other cases (kexec and ftrace) I think are also fine as they
stand - we want to be changing the currently active page tables
there.

So, I really think we do not want to be using pgd_offset_k() here at
all - but we need to find out what's changed to cause (presumably)
mark_rodata_ro() not to be hitting the swapper page tables.  Maybe
some debug in mark_rodata_ro() to find out what current->mm is?
Laura Abbott Nov. 6, 2015, 1:05 a.m. UTC | #6
On 11/05/2015 08:27 AM, Russell King - ARM Linux wrote:
> On Thu, Nov 05, 2015 at 08:20:42AM -0800, Laura Abbott wrote:
>> On 11/05/2015 01:46 AM, Russell King - ARM Linux wrote:
>>> On Wed, Nov 04, 2015 at 05:00:39PM -0800, Laura Abbott wrote:
>>>> Currently, read only permissions are not being applied even
>>>> when CONFIG_DEBUG_RODATA is set. This is because section_update
>>>> uses current->mm for adjusting the page tables. current->mm
>>>> need not be equivalent to the kernel version. Use pgd_offset_k
>>>> to get the proper page directory for updating.
>>>
>>> What are you trying to achieve here?  You can't use these functions
>>> at run time (after the first thread has been spawned) to change
>>> permissions, because there will be multiple copies of the kernel
>>> section mappings, and those copies will not get updated.
>>>
>>> In any case, this change will probably break kexec and ftrace, as
>>> the running thread will no longer see the updated page tables.
>>>
>>
>> I think I was hitting that exact problem with multiple copies
>> not getting updated. The section_update code was being called
>> and I was seeing the tables get updated but nothing was being
>> applied when I tried to write to text or check the debugfs
>> page table. The current flow is:
>>
>> rest_init -> kernel_thread(kernel_init) and from that thread
>> mark_rodata_ro. So mark_rodata_ro is always going to happen
>> in a thread.
>>
>> Do we need to update for both init_mm and the first running
>> thread?
>
> The "first running thread" is merely coincidental for things like kexec.
>
> Hmm.  Actually, I think the existing code _should_ be fine.  At the
> point where mark_rodata_ro() is, we should still be using init_mm, so
> updating the current threads page tables should actually be updating
> the swapper_pg_dir.

That doesn't seem to hold true. Based on what I'm seeing, we lose
the the guarantee of init_mm after the first exec. If usermodehelper
gets called to load a module, that triggers an exec and the kernel
thread is no longer using init_mm after that. I'm testing with the
multi-v7 defconfig which uses the smsc911x driver which loads a
module during initcall. That gets called before mark_rodata_ro so
the init_mm is never updated. I verified that disabling smsc911x
makes things work as expected. I suspect the testing was never done
with a driver that tried to call usermodehelper during init time.

I got as far as narrowing it down that it happens after the usermodehelper
but I wasn't able to pinpoint where exactly the switch happened. It seems
like we need to have the page tables set up before any initcalls
happen otherwise we risk having an exec create stray processes which we
can't update.

Thanks,
Laura
Kees Cook Nov. 6, 2015, 1:15 a.m. UTC | #7
On Thu, Nov 5, 2015 at 5:05 PM, Laura Abbott <labbott@redhat.com> wrote:
> On 11/05/2015 08:27 AM, Russell King - ARM Linux wrote:
>>
>> On Thu, Nov 05, 2015 at 08:20:42AM -0800, Laura Abbott wrote:
>>>
>>> On 11/05/2015 01:46 AM, Russell King - ARM Linux wrote:
>>>>
>>>> On Wed, Nov 04, 2015 at 05:00:39PM -0800, Laura Abbott wrote:
>>>>>
>>>>> Currently, read only permissions are not being applied even
>>>>> when CONFIG_DEBUG_RODATA is set. This is because section_update
>>>>> uses current->mm for adjusting the page tables. current->mm
>>>>> need not be equivalent to the kernel version. Use pgd_offset_k
>>>>> to get the proper page directory for updating.
>>>>
>>>>
>>>> What are you trying to achieve here?  You can't use these functions
>>>> at run time (after the first thread has been spawned) to change
>>>> permissions, because there will be multiple copies of the kernel
>>>> section mappings, and those copies will not get updated.
>>>>
>>>> In any case, this change will probably break kexec and ftrace, as
>>>> the running thread will no longer see the updated page tables.
>>>>
>>>
>>> I think I was hitting that exact problem with multiple copies
>>> not getting updated. The section_update code was being called
>>> and I was seeing the tables get updated but nothing was being
>>> applied when I tried to write to text or check the debugfs
>>> page table. The current flow is:
>>>
>>> rest_init -> kernel_thread(kernel_init) and from that thread
>>> mark_rodata_ro. So mark_rodata_ro is always going to happen
>>> in a thread.
>>>
>>> Do we need to update for both init_mm and the first running
>>> thread?
>>
>>
>> The "first running thread" is merely coincidental for things like kexec.
>>
>> Hmm.  Actually, I think the existing code _should_ be fine.  At the
>> point where mark_rodata_ro() is, we should still be using init_mm, so
>> updating the current threads page tables should actually be updating
>> the swapper_pg_dir.
>
>
> That doesn't seem to hold true. Based on what I'm seeing, we lose
> the the guarantee of init_mm after the first exec. If usermodehelper
> gets called to load a module, that triggers an exec and the kernel
> thread is no longer using init_mm after that. I'm testing with the
> multi-v7 defconfig which uses the smsc911x driver which loads a
> module during initcall. That gets called before mark_rodata_ro so
> the init_mm is never updated. I verified that disabling smsc911x
> makes things work as expected. I suspect the testing was never done
> with a driver that tried to call usermodehelper during init time.

Ooooh. Nice catch. Yeah, my testing didn't include that case.

> I got as far as narrowing it down that it happens after the usermodehelper
> but I wasn't able to pinpoint where exactly the switch happened. It seems
> like we need to have the page tables set up before any initcalls
> happen otherwise we risk having an exec create stray processes which we
> can't update.

Can we just make mark_rodata_ro() a no-op and do the RO setting
earlier when we do the NX setting?

-Kees
diff mbox

Patch

diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c
index 8a63b4c..4bb936a 100644
--- a/arch/arm/mm/init.c
+++ b/arch/arm/mm/init.c
@@ -629,11 +629,9 @@  static struct section_perm ro_perms[] = {
 static inline void section_update(unsigned long addr, pmdval_t mask,
 				  pmdval_t prot)
 {
-	struct mm_struct *mm;
 	pmd_t *pmd;
 
-	mm = current->active_mm;
-	pmd = pmd_offset(pud_offset(pgd_offset(mm, addr), addr), addr);
+	pmd = pmd_offset(pud_offset(pgd_offset_k(addr), addr), addr);
 
 #ifdef CONFIG_ARM_LPAE
 	pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot);