mirror of
https://github.com/torvalds/linux.git
synced 2026-10-07 11:06:03 +02:00
If userspace holds its file descriptor open, it can call PPS_KC_BIND on a
device which has been unplugged, leaving pps_kc_hardpps_dev as a dangling
pointer after close().
After that sequence, PPS_KC_BIND is broken until the system is rebooted,
because the pointer comparison in pps_kc_bind() can never be true.
calling pps_ktimer_init+0x0/0x1000 [pps_ktimer] @ 1081
initcall pps_ktimer_init+0x0/0x1000 [pps_ktimer] returned 0 after 811 usecs
pps pps0: bound kernel consumer: edge=0x1
pps pps0: unbound kernel consumer on device removal
pps pps0: bound kernel consumer: edge=0x1
calling pps_ktimer_init+0x0/0x1000 [pps_ktimer] @ 1085
initcall pps_ktimer_init+0x0/0x1000 [pps_ktimer] returned 0 after 340 usecs
pps pps0: another kernel consumer is already bound
Here is a short reproducer, which uses rmmod of the pps-ktimer testcase
to simulate a device being unplugged:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/pps.h>
#include <errno.h>
#include <err.h>
int main(void)
{
while (1) {
int fd;
if (system("insmod ./pps-ktimer.ko"))
err(1, "insmod failed");
fd = open("/dev/pps0", O_RDWR);
if (fd == -1)
err(1, "open failed");
struct pps_bind_args args = {
.tsformat = PPS_TSFMT_TSPEC,
.edge = PPS_CAPTUREASSERT,
.consumer = PPS_KC_HARDPPS,
};
if (ioctl(fd, PPS_KC_BIND, &args))
err(1, "first PPS_KC_BIND failed");
if (system("rmmod pps-ktimer"))
err(1, "rmmod failed");
if (ioctl(fd, PPS_KC_BIND, &args)) {
if (errno != ENODEV)
err(1, "second PPS_KC_BIND failed");
else
puts("Got ENODEV, kernel is patched");
}
close(fd);
}
}
Fix this by setting a flag when the device is unplugged, returning -ENODEV
from PPS_KC_BIND if the flag is set.
For userspace to encounter this new behavior, it must do something which
breaks the interface today, so this fix shouldn't cause any observable
behavior change for working programs.
Link: https://lore.kernel.org/672778c177ac9b6fdcb445e35c97ac4ca7d1149f.1780506611.git.calvin@wbinvd.org
Signed-off-by: Calvin Owens <calvin@wbinvd.org>
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/cover.1779733602.git.calvin%40wbinvd.org?part=1
Acked-by: Rodolfo Giometti <giometti@enneenne.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
120 lines
3.3 KiB
C
120 lines
3.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* PPS kernel consumer API
|
|
*
|
|
* Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/pps_kernel.h>
|
|
|
|
#include "kc.h"
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
/* state variables to bind kernel consumer */
|
|
static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
|
|
/* PPS API (RFC 2783): current source and mode for kernel consumer */
|
|
static struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */
|
|
static int pps_kc_hardpps_mode; /* mode bits for kernel consumer */
|
|
|
|
/* pps_kc_bind - control PPS kernel consumer binding
|
|
* @pps: the PPS source
|
|
* @bind_args: kernel consumer bind parameters
|
|
*
|
|
* This function is used to bind or unbind PPS kernel consumer according to
|
|
* supplied parameters. Should not be called in interrupt context.
|
|
*/
|
|
int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
|
|
{
|
|
/* Check if another consumer is already bound */
|
|
spin_lock_irq(&pps_kc_hardpps_lock);
|
|
|
|
/*
|
|
* Don't allow PPS_KC_BIND on a removed device.
|
|
*/
|
|
if (pps->kc_removed) {
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (bind_args->edge == 0)
|
|
if (pps_kc_hardpps_dev == pps) {
|
|
pps_kc_hardpps_mode = 0;
|
|
pps_kc_hardpps_dev = NULL;
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
dev_info(&pps->dev, "unbound kernel"
|
|
" consumer\n");
|
|
} else {
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
dev_err(&pps->dev, "selected kernel consumer"
|
|
" is not bound\n");
|
|
return -EINVAL;
|
|
}
|
|
else
|
|
if (pps_kc_hardpps_dev == NULL ||
|
|
pps_kc_hardpps_dev == pps) {
|
|
pps_kc_hardpps_mode = bind_args->edge;
|
|
pps_kc_hardpps_dev = pps;
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
dev_info(&pps->dev, "bound kernel consumer: "
|
|
"edge=0x%x\n", bind_args->edge);
|
|
} else {
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
dev_err(&pps->dev, "another kernel consumer"
|
|
" is already bound\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* pps_kc_remove - unbind kernel consumer on PPS source removal
|
|
* @pps: the PPS source
|
|
*
|
|
* This function is used to disable kernel consumer on PPS source removal
|
|
* if this source was bound to PPS kernel consumer. Can be called on any
|
|
* source safely. Should not be called in interrupt context.
|
|
*/
|
|
void pps_kc_remove(struct pps_device *pps)
|
|
{
|
|
spin_lock_irq(&pps_kc_hardpps_lock);
|
|
|
|
pps->kc_removed = true;
|
|
if (pps == pps_kc_hardpps_dev) {
|
|
pps_kc_hardpps_mode = 0;
|
|
pps_kc_hardpps_dev = NULL;
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
dev_info(&pps->dev, "unbound kernel consumer"
|
|
" on device removal\n");
|
|
} else
|
|
spin_unlock_irq(&pps_kc_hardpps_lock);
|
|
}
|
|
|
|
/* pps_kc_event - call hardpps() on PPS event
|
|
* @pps: the PPS source
|
|
* @ts: PPS event timestamp
|
|
* @event: PPS event edge
|
|
*
|
|
* This function calls hardpps() when an event from bound PPS source occurs.
|
|
*/
|
|
void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,
|
|
int event)
|
|
{
|
|
unsigned long flags;
|
|
|
|
/* Pass some events to kernel consumer if activated */
|
|
spin_lock_irqsave(&pps_kc_hardpps_lock, flags);
|
|
if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)
|
|
hardpps(&ts->ts_real, &ts->ts_raw);
|
|
spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);
|
|
}
|