• Luis R. Rodriguez's avatar
    firmware: avoid invalid fallback aborts by using killable wait · 67e1a98e
    Luis R. Rodriguez authored
    commit 260d9f2f upstream.
    
    Commit 0cb64249 ("firmware_loader: abort request if wait_for_completion
    is interrupted") added via 4.0 added support to abort the fallback mechanism
    when a signal was detected and wait_for_completion_interruptible() returned
    -ERESTARTSYS -- for instance when a user hits CTRL-C. The abort was overly
    *too* effective.
    
    When a child process terminates (successful or not) the signal SIGCHLD can
    be sent to the parent process which ran the child in the background and
    later triggered a sync request for firmware through a sysfs interface which
    relies on the fallback mechanism. This signal in turn can be recieved by the
    interruptible wait we constructed on firmware_class and detects it as an
    abort *before* userspace could get a chance to write the firmware. Upon
    failure -EAGAIN is returned, so userspace is also kept in the dark about
    exactly what happened.
    
    We can reproduce the issue with the fw_fallback.sh selftest:
    
    Before this patch:
    $ sudo tools/testing/selftests/firmware/fw_fallback.sh
    ...
    tools/testing/selftests/firmware/fw_fallback.sh: error - sync firmware request cancelled due to SIGCHLD
    
    After this patch:
    $ sudo tools/testing/selftests/firmware/fw_fallback.sh
    ...
    tools/testing/selftests/firmware/fw_fallback.sh: SIGCHLD on sync ignored as expected
    
    Fix this by making the wait killable -- only killable by SIGKILL (kill -9).
    We loose the ability to allow userspace to cancel a write with CTRL-C
    (SIGINT), however its been decided the compromise to require SIGKILL is
    worth the gains.
    
    Chances of this issue occuring are low due to the number of drivers upstream
    exclusively relying on the fallback mechanism for firmware (2 drivers),
    however this is observed in the field with custom drivers with sysfs
    triggers to load firmware. Only distributions relying on the fallback
    mechanism are impacted as well. An example reported issue was on Android,
    as follows:
    
    1) Android init (pid=1) fork()s (say pid=42) [this child process is totally
       unrelated to firmware loading, it could be sleep 2; for all we care ]
    2) Android init (pid=1) does a write() on a (driver custom) sysfs file which
       ends up calling request_firmware() kernel side
    3) The firmware loading fallback mechanism is used, the request is sent to
       userspace and pid 1 waits in the kernel on wait_*
    4) before firmware loading completes pid 42 dies (for any reason, even
       normal termination)
    5) Kernel delivers SIGCHLD to pid=1 to tell it a child has died, which
       causes -ERESTARTSYS to be returned from wait_*
    6) The kernel's wait aborts and return -EAGAIN for the
       request_firmware() caller.
    
    Fixes: 0cb64249 ("firmware_loader: abort request if wait_for_completion is interrupted")
    Suggested-by: default avatar"Eric W. Biederman" <ebiederm@xmission.com>
    Suggested-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
    Tested-by: default avatarMartin Fuzzey <mfuzzey@parkeon.com>
    Reported-by: default avatarMartin Fuzzey <mfuzzey@parkeon.com>
    Signed-off-by: default avatarLuis R. Rodriguez <mcgrof@kernel.org>
    Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
    67e1a98e
firmware_class.c 43.2 KB