mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07: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>
124 lines
3.0 KiB
C
124 lines
3.0 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* PPS API kernel header
|
|
*
|
|
* Copyright (C) 2009 Rodolfo Giometti <giometti@linux.it>
|
|
*/
|
|
|
|
#ifndef LINUX_PPS_KERNEL_H
|
|
#define LINUX_PPS_KERNEL_H
|
|
|
|
#include <linux/pps.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/time.h>
|
|
|
|
/*
|
|
* Global defines
|
|
*/
|
|
|
|
struct pps_device;
|
|
|
|
/* The specific PPS source info */
|
|
struct pps_source_info {
|
|
char name[PPS_MAX_NAME_LEN]; /* symbolic name */
|
|
char path[PPS_MAX_NAME_LEN]; /* path of connected device */
|
|
int mode; /* PPS allowed mode */
|
|
|
|
void (*echo)(struct pps_device *pps,
|
|
int event, void *data); /* PPS echo function */
|
|
|
|
struct module *owner;
|
|
struct device *dev; /* Parent device for device_create */
|
|
};
|
|
|
|
struct pps_event_time {
|
|
#ifdef CONFIG_NTP_PPS
|
|
struct timespec64 ts_raw;
|
|
#endif /* CONFIG_NTP_PPS */
|
|
struct timespec64 ts_real;
|
|
};
|
|
|
|
/* The main struct */
|
|
struct pps_device {
|
|
struct pps_source_info info; /* PSS source info */
|
|
|
|
struct pps_kparams params; /* PPS current params */
|
|
|
|
__u32 assert_sequence; /* PPS assert event seq # */
|
|
__u32 clear_sequence; /* PPS clear event seq # */
|
|
struct pps_ktime assert_tu;
|
|
struct pps_ktime clear_tu;
|
|
int current_mode; /* PPS mode at event time */
|
|
|
|
unsigned int last_ev; /* last PPS event id */
|
|
unsigned int last_fetched_ev; /* last fetched PPS event id */
|
|
wait_queue_head_t queue; /* PPS event queue */
|
|
|
|
unsigned int id; /* PPS source unique ID */
|
|
void const *lookup_cookie; /* For pps_lookup_dev() only */
|
|
struct device dev;
|
|
struct fasync_struct *async_queue; /* fasync method */
|
|
spinlock_t lock;
|
|
bool kc_removed;
|
|
};
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
extern const struct attribute_group *pps_groups[];
|
|
|
|
/*
|
|
* Internal functions.
|
|
*
|
|
* These are not actually part of the exported API, but this is a
|
|
* convenient header file to put them in.
|
|
*/
|
|
|
|
extern int pps_register_cdev(struct pps_device *pps);
|
|
extern void pps_unregister_cdev(struct pps_device *pps);
|
|
|
|
/*
|
|
* Exported functions
|
|
*/
|
|
|
|
extern struct pps_device *pps_register_source(
|
|
struct pps_source_info *info, int default_params);
|
|
extern void pps_unregister_source(struct pps_device *pps);
|
|
extern void pps_event(struct pps_device *pps,
|
|
struct pps_event_time *ts, int event, void *data);
|
|
/* Look up a pps_device by magic cookie */
|
|
struct pps_device *pps_lookup_dev(void const *cookie);
|
|
|
|
static inline void timespec_to_pps_ktime(struct pps_ktime *kt,
|
|
struct timespec64 ts)
|
|
{
|
|
kt->sec = ts.tv_sec;
|
|
kt->nsec = ts.tv_nsec;
|
|
}
|
|
|
|
static inline void pps_get_ts(struct pps_event_time *ts)
|
|
{
|
|
#ifdef CONFIG_NTP_PPS
|
|
struct system_time_snapshot snap;
|
|
|
|
ktime_get_snapshot_id(CLOCK_REALTIME, &snap);
|
|
ts->ts_real = ktime_to_timespec64(snap.systime);
|
|
ts->ts_raw = ktime_to_timespec64(snap.monoraw);
|
|
#else
|
|
ktime_get_real_ts64(&ts->ts_real);
|
|
#endif
|
|
}
|
|
|
|
/* Subtract known time delay from PPS event time(s) */
|
|
static inline void pps_sub_ts(struct pps_event_time *ts, struct timespec64 delta)
|
|
{
|
|
ts->ts_real = timespec64_sub(ts->ts_real, delta);
|
|
#ifdef CONFIG_NTP_PPS
|
|
ts->ts_raw = timespec64_sub(ts->ts_raw, delta);
|
|
#endif
|
|
}
|
|
|
|
#endif /* LINUX_PPS_KERNEL_H */
|