diff mbox

[v2,1/5] irq: set {msi_domain,syscore}_ops as __ro_after_init

Message ID 20170211013758.3288-1-me@jessfraz.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jess Frazelle Feb. 11, 2017, 1:37 a.m. UTC
Marked msi_domain_ops structs as __ro_after_init when called only during init.
Marked syscore_ops structs as __ro_after_init when register_syscore_ops was
called only during init. Most of the caller functions were already annotated as
__init.
unregister_syscore_ops() was never called on these syscore_ops.
This protects the data structure from accidental corruption.

Suggested-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Jess Frazelle <me@jessfraz.com>
---
 kernel/irq/generic-chip.c | 2 +-
 kernel/irq/msi.c          | 2 +-
 kernel/irq/pm.c           | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

--
2.11.0

Comments

Thomas Gleixner Feb. 11, 2017, 9:14 a.m. UTC | #1
On Fri, 10 Feb 2017, Jess Frazelle wrote:

> Marked msi_domain_ops structs as __ro_after_init when called only during init.
> Marked syscore_ops structs as __ro_after_init when register_syscore_ops was
> called only during init. Most of the caller functions were already annotated as
> __init.
> unregister_syscore_ops() was never called on these syscore_ops.
> This protects the data structure from accidental corruption.

Please be more careful with your changelogs. They should not start with
telling WHAT you have done. The WHAT we can see from the patch.

The interesting information which belongs into the changelog is: WHY and
which problem does it solve or which enhancement this is. Let me give you
an example:

  Function pointers are a target for attacks especially when they are
  located in statically allocated data structures. Some of these data
  structures are only modified during init and therefor can be made read
  only after init.

  struct msi_domain_ops can be made read only after init because they are
  only updated in the registration case.

  struct syscore_ops can be made read only after init when they are only
  registered, but never unregistered.

So this would be a proper change log explaning the patch.

Emphasis on WOULD, See below.

> -static struct syscore_ops irq_gc_syscore_ops = {
> +static struct syscore_ops irq_gc_syscore_ops __ro_after_init = {
>  	.suspend = irq_gc_suspend,
>  	.resume = irq_gc_resume,
>  	.shutdown = irq_gc_shutdown,

I seriously doubt that syscore_ops can be made __ro_after_init at all.

Assume the following:

last_init_function()
  register_syscore_ops(&a_ops)
    list_add(&a_ops->node, list);

apply_ro_after_init()
  // a_ops are now read only

cpuhotplug happens
  register_syscore_ops(&b_ops)
    list_add(&b_ops->node, list);

    ===> Kernel crashes with a write access on RO memory because it tries
    	 to link b_ops to a_ops.

The same is true for cpuhotunplug operations.

> -static struct msi_domain_ops msi_domain_ops_default = {
> +static struct msi_domain_ops msi_domain_ops_default __ro_after_init = {

This is pointless and just tells me that you did a mechanical search for
these ops and then blindly added __ro_after_init instead of analysing how
msi_domain_ops_default is used.

msi_domain_ops_default are never ever modified, so they should be made
'const' and not __ro_after_init. It's not that hard to figure that out from
the code.

Thanks,

	tglx
Thomas Gleixner Feb. 11, 2017, 9:23 a.m. UTC | #2
On Sat, 11 Feb 2017, Thomas Gleixner wrote:

> On Fri, 10 Feb 2017, Jess Frazelle wrote:
> 
> > Marked msi_domain_ops structs as __ro_after_init when called only during init.
> > Marked syscore_ops structs as __ro_after_init when register_syscore_ops was
> > called only during init. Most of the caller functions were already annotated as
> > __init.
> > unregister_syscore_ops() was never called on these syscore_ops.
> > This protects the data structure from accidental corruption.
> 
> Please be more careful with your changelogs. They should not start with
> telling WHAT you have done. The WHAT we can see from the patch.
> 
> The interesting information which belongs into the changelog is: WHY and
> which problem does it solve or which enhancement this is. Let me give you
> an example:
> 
>   Function pointers are a target for attacks especially when they are
>   located in statically allocated data structures. Some of these data
>   structures are only modified during init and therefor can be made read
>   only after init.
> 
>   struct msi_domain_ops can be made read only after init because they are
>   only updated in the registration case.
> 
>   struct syscore_ops can be made read only after init when they are only
>   registered, but never unregistered.
> 
> So this would be a proper change log explaning the patch.
> 
> Emphasis on WOULD, See below.
> 
> > -static struct syscore_ops irq_gc_syscore_ops = {
> > +static struct syscore_ops irq_gc_syscore_ops __ro_after_init = {
> >  	.suspend = irq_gc_suspend,
> >  	.resume = irq_gc_resume,
> >  	.shutdown = irq_gc_shutdown,
> 
> I seriously doubt that syscore_ops can be made __ro_after_init at all.
> 
> Assume the following:
> 
> last_init_function()
>   register_syscore_ops(&a_ops)
>     list_add(&a_ops->node, list);
> 
> apply_ro_after_init()
>   // a_ops are now read only
> 
> cpuhotplug happens
>   register_syscore_ops(&b_ops)
>     list_add(&b_ops->node, list);
> 
>     ===> Kernel crashes with a write access on RO memory because it tries
>     	 to link b_ops to a_ops.
> 
> The same is true for cpuhotunplug operations.

Sorry, cpuhotplug was the wrong example, but loading or unloading the KVM
modules will have that effect.

See vmx_init()/exit() and kvm_init()/exit() for reference.

Thanks,

	tglx
Jess Frazelle Feb. 11, 2017, 10:48 a.m. UTC | #3
On February 11, 2017 1:14:52 AM PST, Thomas Gleixner <tglx@linutronix.de> wrote:
>On Fri, 10 Feb 2017, Jess Frazelle wrote:
>
>> Marked msi_domain_ops structs as __ro_after_init when called only
>during init.
>> Marked syscore_ops structs as __ro_after_init when
>register_syscore_ops was
>> called only during init. Most of the caller functions were already
>annotated as
>> __init.
>> unregister_syscore_ops() was never called on these syscore_ops.
>> This protects the data structure from accidental corruption.
>
>Please be more careful with your changelogs. They should not start with
>telling WHAT you have done. The WHAT we can see from the patch.
>
>The interesting information which belongs into the changelog is: WHY
>and
>which problem does it solve or which enhancement this is. Let me give
>you
>an example:
>
>  Function pointers are a target for attacks especially when they are
>  located in statically allocated data structures. Some of these data
> structures are only modified during init and therefor can be made read
>  only after init.
>
>struct msi_domain_ops can be made read only after init because they are
>  only updated in the registration case.
>
> struct syscore_ops can be made read only after init when they are only
>  registered, but never unregistered.
>
>So this would be a proper change log explaning the patch.

Thanks for the clarification.

>
>Emphasis on WOULD, See below.
>
>> -static struct syscore_ops irq_gc_syscore_ops = {
>> +static struct syscore_ops irq_gc_syscore_ops __ro_after_init = {
>>  	.suspend = irq_gc_suspend,
>>  	.resume = irq_gc_resume,
>>  	.shutdown = irq_gc_shutdown,
>
>I seriously doubt that syscore_ops can be made __ro_after_init at all.
>
>Assume the following:
>
>last_init_function()
>  register_syscore_ops(&a_ops)
>    list_add(&a_ops->node, list);
>
>apply_ro_after_init()
>  // a_ops are now read only
>
>cpuhotplug happens
>  register_syscore_ops(&b_ops)
>    list_add(&b_ops->node, list);
>
>  ===> Kernel crashes with a write access on RO memory because it tries
>    	 to link b_ops to a_ops.
>
>The same is true for cpuhotunplug operations.

This makes sense. Will remove.

>
>> -static struct msi_domain_ops msi_domain_ops_default = {
>> +static struct msi_domain_ops msi_domain_ops_default __ro_after_init
>= {
>
>This is pointless and just tells me that you did a mechanical search
>for
>these ops and then blindly added __ro_after_init instead of analysing
>how
>msi_domain_ops_default is used.
>
>msi_domain_ops_default are never ever modified, so they should be made
>'const' and not __ro_after_init. It's not that hard to figure that out
>from
>the code.

Will change to a const.

>
>Thanks,
>
>	tglx
Thomas Gleixner Feb. 11, 2017, noon UTC | #4
On Sat, 11 Feb 2017, Jess Frazelle wrote:
> On February 11, 2017 1:14:52 AM PST, Thomas Gleixner <tglx@linutronix.de> wrote:
> >The same is true for cpuhotunplug operations.
> 
> This makes sense. Will remove.

That's true for all other patches touching sysops as well. But instead of
giving up I'd recommend to look into the following:

Go through all callsites which use un/register_syscore_ops() and figure out
how many of them are possibly called post init. From a quick grep I can
only find the KVM module, but there might be more.

Lets assume it's KVM only. So you could do the following:

Put something like this into virt/kvm/kvm_main.c, which is a builtin file

static struct syscore_ops ops __ro_after_init = {
       ....
};

int __init foo()
{
    register_ops(&ops);
}

and because we know that kvm is single instance you can just have:

static struct syscore_ops *kvm_ops;

void kvm_set_sysop(*vmx_ops)
{
	kvm_ops = ops;
}

and then have the kvm_syscore callbacks:

static callback()
{
	if (kvm_ops)
	   kvm_ops->callback()
}

Sanity checks and serialization omitted. Then switch kvm_exit/init over to
it.

After that you can make all syscore_ops __ro_after_init, remove the export
from (un)register_syscore_ops() and make that __init.

Not much of an effort and probably worth the trouble.

Thanks,

	tglx
Jess Frazelle Feb. 11, 2017, 12:17 p.m. UTC | #5
Thanks, I'll check them out.

On Sat, Feb 11, 2017 at 4:00 AM Thomas Gleixner <tglx@linutronix.de> wrote:
>
> On Sat, 11 Feb 2017, Jess Frazelle wrote:
> > On February 11, 2017 1:14:52 AM PST, Thomas Gleixner <tglx@linutronix.de> wrote:
> > >The same is true for cpuhotunplug operations.
> >
> > This makes sense. Will remove.
>
> That's true for all other patches touching sysops as well. But instead of
> giving up I'd recommend to look into the following:
>
> Go through all callsites which use un/register_syscore_ops() and figure out
> how many of them are possibly called post init. From a quick grep I can
> only find the KVM module, but there might be more.
>
> Lets assume it's KVM only. So you could do the following:
>
> Put something like this into virt/kvm/kvm_main.c, which is a builtin file
>
> static struct syscore_ops ops __ro_after_init = {
>        ....
> };
>
> int __init foo()
> {
>     register_ops(&ops);
> }
>
> and because we know that kvm is single instance you can just have:
>
> static struct syscore_ops *kvm_ops;
>
> void kvm_set_sysop(*vmx_ops)
> {
>         kvm_ops = ops;
> }
>
> and then have the kvm_syscore callbacks:
>
> static callback()
> {
>         if (kvm_ops)
>            kvm_ops->callback()
> }
>
> Sanity checks and serialization omitted. Then switch kvm_exit/init over to
> it.
>
> After that you can make all syscore_ops __ro_after_init, remove the export
> from (un)register_syscore_ops() and make that __init.
>
> Not much of an effort and probably worth the trouble.
>
> Thanks,
>
>         tglx
>
diff mbox

Patch

diff --git a/kernel/irq/generic-chip.c b/kernel/irq/generic-chip.c
index ee32870079c9..cca63dbaabea 100644
--- a/kernel/irq/generic-chip.c
+++ b/kernel/irq/generic-chip.c
@@ -623,7 +623,7 @@  static void irq_gc_shutdown(void)
 	}
 }

-static struct syscore_ops irq_gc_syscore_ops = {
+static struct syscore_ops irq_gc_syscore_ops __ro_after_init = {
 	.suspend = irq_gc_suspend,
 	.resume = irq_gc_resume,
 	.shutdown = irq_gc_shutdown,
diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c
index ee230063f033..0e5b723f710f 100644
--- a/kernel/irq/msi.c
+++ b/kernel/irq/msi.c
@@ -217,7 +217,7 @@  static int msi_domain_ops_check(struct irq_domain *domain,
 	return 0;
 }

-static struct msi_domain_ops msi_domain_ops_default = {
+static struct msi_domain_ops msi_domain_ops_default __ro_after_init = {
 	.get_hwirq	= msi_domain_ops_get_hwirq,
 	.msi_init	= msi_domain_ops_init,
 	.msi_check	= msi_domain_ops_check,
diff --git a/kernel/irq/pm.c b/kernel/irq/pm.c
index cea1de0161f1..d6b889bed323 100644
--- a/kernel/irq/pm.c
+++ b/kernel/irq/pm.c
@@ -185,7 +185,7 @@  static void irq_pm_syscore_resume(void)
 	resume_irqs(true);
 }

-static struct syscore_ops irq_pm_syscore_ops = {
+static struct syscore_ops irq_pm_syscore_ops __ro_after_init = {
 	.resume		= irq_pm_syscore_resume,
 };