Re: [POC 19/23] module/livepatch: Allow to use exported symbols from livepatch module for "vmlinux"

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 07, 2020 at 09:33:08AM +0200, Miroslav Benes wrote:
> On Mon, 6 Apr 2020, Joe Lawrence wrote:
> 
> > On Fri, Jan 17, 2020 at 04:03:19PM +0100, Petr Mladek wrote:
> > > HINT: Get some coffee before reading this commit message.
> > > 
> > > [ ... snip ... ]
> > > 
> > > B. Livepatch module using an exported symbol from the patched module.
> > > 
> > >    It should be avoided even with the non-split livepatch module. The module
> > >    loader automatically takes reference to make sure the modules are
> > >    unloaded in the right order. This would basically prevent the livepatched
> > >    module from unloading.
> > > 
> > >    Note that it would be perfectly safe to remove this automatic
> > >    dependency. The livepatch framework makes sure that the livepatch
> > >    module is loaded only when the patched one is loaded. But it cannot
> > >    be implemented easily, see below.
> > 
> > Do you envision klp-convert providing this functionality?
> > 
> > [ ... snip ... ]
> 
> That is one way to get around the dependency problem. And I think it 
> should work even with the PoC. It should (and I don't remember all details 
> now unfortunately) guarantee that the patched module is always available 
> for the livepatch and since there is no explicit dependency, the recursion 
> issue is gone.
> 
> However, I think the goal was to follow the most natural road and leverage 
> the existing dependency system. Meaning, since the presence of a patched 
> module in the system before its patching is guaranteed now, there is no 
> reason not to use its exported symbols directly like anywhere else. But it 
> introduces the recursion problem, so we may drop it.
> 
> > FWIW, I have been working an updated klp-convert for v5.6 that includes
> > a bunch of fixes and such... modifying it to convert module-export
> > references like this was quite easy.
> 
> *THUMBS UP* :)

Hi Miroslav,

Perhaps the following bug report is premature, but it was far easier to
start playing with the PoC code than audit all the individual race
conditions :) This came out of the mentioned klp-convert rebase that I
later put on-top of Petr's PoC, and then started writing up some more
selftests to see how the new livepatching world might look.

Forgive me if I'm violating some obvious assumption that the PoC makes,
but I think the experiment may still be useful in pointing out a
problematic use-case / potential pitfall a livepatch author might
encounter.

tl;dr: prepare_coming_module() calls jump_label_add_module() *after* it
       calls klp_module_coming().  In the PoC, the latter function now
       searches for any livepatches that may apply to the loading
       module and tries to load them before proceeding.  If one of
       those livepatch modules references any static key defined by the
       originally loading module, the static key entry structures may
       not be setup correctly.


The test case:

- A kernel module defines a static key called test_klp_true_key and on
  __init, calls static_branch_disable().  I don't think the __init part
  is required for this case, however it was just the easiest way to
  write the test that I was working on at the time.  AFAIK we could
  change the key any where else with the same problem.

- A livepatch module that references test_klp_true_key.  The overarching
  test case was for the key's module owner (target) and 
  livepatch (livepatch__target) to toggle the key and for both target
  and livepatch__target modules to be patched accordingly.

- klp-convert was enhanced to modify relocations to test_klp_true_key in
  both .text and __jump_table sections.  It previously could not handle
  this scenario.


Pseudocode
==========

target.c
--------
static DEFINE_STATIC_KEY_TRUE(test_klp_true_key);
...
__init function() {
	static_branch_disable(&test_klp_true_key);
}

livepatch__target.c
-------------------
extern struct static_key_true test_klp_true_key;
...
pr_info("static_branch_likely(&test_klp_true_key) is %s\n",
	static_branch_likely(&test_klp_true_key) ? "true" : "false");


Test
====

% modprobe livepatch    # livepatch__target only loads when target loads
% modprobe target

(crash in jump_label_update)


Callchain and notes
===================

(livepatch is already loaded)

target
------
load_module
  apply_relocations
  post_relocations
    module_finalize
      jump_label_apply_nops
  complete_formation
    mod->state = MODULE_STATE_COMING
  prepare_coming_module
    klp_module_coming
      klp_try_load_object
        patch_name = livepatch, obj_name = target

    livepatch__target
    -----------------
    load_module
      apply_relocations
      post_relocations
        module_finalize
          jump_label_apply_nops
      complete_formation
        mod->state = MODULE_STATE_COMING
      prepare_coming_module
        blocking_notifier_call_chain(MODULE_STATE_COMING)
          jump_label_module_notify (MODULE_STATE_COMING)
            jump_label_add_module

              first time processing test_klp_true_key, within_module()
              returns false (the key is defined in the other module),
              and we treat it as !static_key_linked() so the code goes
              ahead and makes it linked

              static_key_set_linked
                key->type |= JUMP_TYPE_LINKED

      do_init_module
        mod->state = MODULE_STATE_LIVE;
        blocking_notifier_call_chain(MODULE_STATE_LIVE)
          jump_label_module_notify (MODULE_STATE_LIVE)

target
------
  (prepare_coming_module cont...)
    blocking_notifier_call_chain(MODULE_STATE_COMING)
     jump_label_module_notify(MODULE_STATE_COMING)
       jump_label_add_module

         second time processing test_klp_true_key, within_module()
	 returns true this time and we go straight to
	 static_key_set_entries(), which is careful enough to verify the
         that the entries aren't already linked, but not the key itself

         static_key_set_entries
           WARN_ON_ONCE((unsigned long)entries & JUMP_TYPE_MASK)


  do_init_module
    mod->state = MODULE_STATE_LIVE;
    blocking_notifier_call_chain(MODULE_STATE_LIVE)
      jump_label_module_notify (MODULE_STATE_LIVE)


Ok, so it seems that jump_label_add_module() assumes that any given key
that isn't present in said module, is assumed to have already been
initialized and therefore it enters that convoluted JUMP_TYPE_LINKED
code.

When we later try call jump_label_add_module() for the target module,
the same function assumes that the key is not linked and we're left with
a weird static_key_mod entry that later crashes the box.

tl;dr2: Could we safely reorder klp_module_{coming,going}() with respect
        to the module_notifier callbacks?  i.e.

        blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod);
        klp_module_coming(mod);
          ... and ...
        klp_module_going(mod);
        blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod);

This fixes the above test case, but I wasn't sure if it now violates
some other part of the PoC or consistency model.

-- Joe




[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux Kernel]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux SCSI]

  Powered by Linux