Files
linux/drivers/net/ethernet/cadence/macb_ptp.c
Fedor Pchelkin 8da13e6d63 net: macb: fix use-after-free access to PTP clock
PTP clock is registered on every opening of the interface and destroyed on
every closing.  However it may be accessed via get_ts_info ethtool call
which is possible while the interface is just present in the kernel.

BUG: KASAN: use-after-free in ptp_clock_index+0x47/0x50 drivers/ptp/ptp_clock.c:426
Read of size 4 at addr ffff8880194345cc by task syz.0.6/948

CPU: 1 PID: 948 Comm: syz.0.6 Not tainted 6.1.164+ #109
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.1-0-g3208b098f51a-prebuilt.qemu.org 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:88 [inline]
 dump_stack_lvl+0x8d/0xba lib/dump_stack.c:106
 print_address_description mm/kasan/report.c:316 [inline]
 print_report+0x17f/0x496 mm/kasan/report.c:420
 kasan_report+0xd9/0x180 mm/kasan/report.c:524
 ptp_clock_index+0x47/0x50 drivers/ptp/ptp_clock.c:426
 gem_get_ts_info+0x138/0x1e0 drivers/net/ethernet/cadence/macb_main.c:3349
 macb_get_ts_info+0x68/0xb0 drivers/net/ethernet/cadence/macb_main.c:3371
 __ethtool_get_ts_info+0x17c/0x260 net/ethtool/common.c:558
 ethtool_get_ts_info net/ethtool/ioctl.c:2367 [inline]
 __dev_ethtool net/ethtool/ioctl.c:3017 [inline]
 dev_ethtool+0x2b05/0x6290 net/ethtool/ioctl.c:3095
 dev_ioctl+0x637/0x1070 net/core/dev_ioctl.c:510
 sock_do_ioctl+0x20d/0x2c0 net/socket.c:1215
 sock_ioctl+0x577/0x6d0 net/socket.c:1320
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:870 [inline]
 __se_sys_ioctl fs/ioctl.c:856 [inline]
 __x64_sys_ioctl+0x18c/0x210 fs/ioctl.c:856
 do_syscall_x64 arch/x86/entry/common.c:46 [inline]
 do_syscall_64+0x35/0x80 arch/x86/entry/common.c:76
 entry_SYSCALL_64_after_hwframe+0x6e/0xd8
 </TASK>

Allocated by task 457:
 kmalloc include/linux/slab.h:563 [inline]
 kzalloc include/linux/slab.h:699 [inline]
 ptp_clock_register+0x144/0x10e0 drivers/ptp/ptp_clock.c:235
 gem_ptp_init+0x46f/0x930 drivers/net/ethernet/cadence/macb_ptp.c:375
 macb_open+0x901/0xd10 drivers/net/ethernet/cadence/macb_main.c:2920
 __dev_open+0x2ce/0x500 net/core/dev.c:1501
 __dev_change_flags+0x56a/0x740 net/core/dev.c:8651
 dev_change_flags+0x92/0x170 net/core/dev.c:8722
 do_setlink+0xaf8/0x3a80 net/core/rtnetlink.c:2833
 __rtnl_newlink+0xbf4/0x1940 net/core/rtnetlink.c:3608
 rtnl_newlink+0x63/0xa0 net/core/rtnetlink.c:3655
 rtnetlink_rcv_msg+0x3c6/0xed0 net/core/rtnetlink.c:6150
 netlink_rcv_skb+0x15d/0x430 net/netlink/af_netlink.c:2511
 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
 netlink_unicast+0x6d7/0xa30 net/netlink/af_netlink.c:1344
 netlink_sendmsg+0x97e/0xeb0 net/netlink/af_netlink.c:1872
 sock_sendmsg_nosec net/socket.c:718 [inline]
 __sock_sendmsg+0x14b/0x180 net/socket.c:730
 __sys_sendto+0x320/0x3b0 net/socket.c:2152
 __do_sys_sendto net/socket.c:2164 [inline]
 __se_sys_sendto net/socket.c:2160 [inline]
 __x64_sys_sendto+0xdc/0x1b0 net/socket.c:2160
 do_syscall_x64 arch/x86/entry/common.c:46 [inline]
 do_syscall_64+0x35/0x80 arch/x86/entry/common.c:76
 entry_SYSCALL_64_after_hwframe+0x6e/0xd8

Freed by task 938:
 kasan_slab_free include/linux/kasan.h:177 [inline]
 slab_free_hook mm/slub.c:1729 [inline]
 slab_free_freelist_hook mm/slub.c:1755 [inline]
 slab_free mm/slub.c:3687 [inline]
 __kmem_cache_free+0xbc/0x320 mm/slub.c:3700
 device_release+0xa0/0x240 drivers/base/core.c:2507
 kobject_cleanup lib/kobject.c:681 [inline]
 kobject_release lib/kobject.c:712 [inline]
 kref_put include/linux/kref.h:65 [inline]
 kobject_put+0x1cd/0x350 lib/kobject.c:729
 put_device+0x1b/0x30 drivers/base/core.c:3805
 ptp_clock_unregister+0x171/0x270 drivers/ptp/ptp_clock.c:391
 gem_ptp_remove+0x4e/0x1f0 drivers/net/ethernet/cadence/macb_ptp.c:404
 macb_close+0x1c8/0x270 drivers/net/ethernet/cadence/macb_main.c:2966
 __dev_close_many+0x1b9/0x310 net/core/dev.c:1585
 __dev_close net/core/dev.c:1597 [inline]
 __dev_change_flags+0x2bb/0x740 net/core/dev.c:8649
 dev_change_flags+0x92/0x170 net/core/dev.c:8722
 dev_ifsioc+0x151/0xe00 net/core/dev_ioctl.c:326
 dev_ioctl+0x33e/0x1070 net/core/dev_ioctl.c:572
 sock_do_ioctl+0x20d/0x2c0 net/socket.c:1215
 sock_ioctl+0x577/0x6d0 net/socket.c:1320
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:870 [inline]
 __se_sys_ioctl fs/ioctl.c:856 [inline]
 __x64_sys_ioctl+0x18c/0x210 fs/ioctl.c:856
 do_syscall_x64 arch/x86/entry/common.c:46 [inline]
 do_syscall_64+0x35/0x80 arch/x86/entry/common.c:76
 entry_SYSCALL_64_after_hwframe+0x6e/0xd8

Set the PTP clock pointer to NULL after unregistering.

Fixes: c2594d804d ("macb: Common code to enable ptp support for MACB/GEM")
Cc: stable@vger.kernel.org
Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
Link: https://patch.msgid.link/20260316103826.74506-1-pchelkin@ispras.ru
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-03-18 17:18:47 -07:00

471 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* 1588 PTP support for Cadence GEM device.
*
* Copyright (C) 2017 Cadence Design Systems - https://www.cadence.com
*
* Authors: Rafal Ozieblo <rafalo@cadence.com>
* Bartosz Folta <bfolta@cadence.com>
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/time64.h>
#include <linux/ptp_classify.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/circ_buf.h>
#include <linux/spinlock.h>
#include "macb.h"
#define GEM_PTP_TIMER_NAME "gem-ptp-timer"
static struct macb_dma_desc_ptp *macb_ptp_desc(struct macb *bp,
struct macb_dma_desc *desc)
{
if (!macb_dma_ptp(bp))
return NULL;
if (macb_dma64(bp))
return (struct macb_dma_desc_ptp *)
((u8 *)desc + sizeof(struct macb_dma_desc)
+ sizeof(struct macb_dma_desc_64));
else
return (struct macb_dma_desc_ptp *)
((u8 *)desc + sizeof(struct macb_dma_desc));
}
static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
unsigned long flags;
long first, second;
u32 secl, sech;
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
ptp_read_system_prets(sts);
first = gem_readl(bp, TN);
ptp_read_system_postts(sts);
secl = gem_readl(bp, TSL);
sech = gem_readl(bp, TSH);
second = gem_readl(bp, TN);
/* test for nsec rollover */
if (first > second) {
/* if so, use later read & re-read seconds
* (assume all done within 1s)
*/
ptp_read_system_prets(sts);
ts->tv_nsec = gem_readl(bp, TN);
ptp_read_system_postts(sts);
secl = gem_readl(bp, TSL);
sech = gem_readl(bp, TSH);
} else {
ts->tv_nsec = first;
}
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
ts->tv_sec = (((u64)sech << GEM_TSL_SIZE) | secl)
& TSU_SEC_MAX_VAL;
return 0;
}
static int gem_tsu_set_time(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
unsigned long flags;
u32 ns, sech, secl;
secl = (u32)ts->tv_sec;
sech = (ts->tv_sec >> GEM_TSL_SIZE) & ((1 << GEM_TSH_SIZE) - 1);
ns = ts->tv_nsec;
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
/* TSH doesn't latch the time and no atomicity! */
gem_writel(bp, TN, 0); /* clear to avoid overflow */
gem_writel(bp, TSH, sech);
/* write lower bits 2nd, for synchronized secs update */
gem_writel(bp, TSL, secl);
gem_writel(bp, TN, ns);
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
return 0;
}
static int gem_tsu_incr_set(struct macb *bp, struct tsu_incr *incr_spec)
{
unsigned long flags;
/* tsu_timer_incr register must be written after
* the tsu_timer_incr_sub_ns register and the write operation
* will cause the value written to the tsu_timer_incr_sub_ns register
* to take effect.
*/
spin_lock_irqsave(&bp->tsu_clk_lock, flags);
/* RegBit[15:0] = Subns[23:8]; RegBit[31:24] = Subns[7:0] */
gem_writel(bp, TISUBN, GEM_BF(SUBNSINCRL, incr_spec->sub_ns) |
GEM_BF(SUBNSINCRH, (incr_spec->sub_ns >>
GEM_SUBNSINCRL_SIZE)));
gem_writel(bp, TI, GEM_BF(NSINCR, incr_spec->ns));
spin_unlock_irqrestore(&bp->tsu_clk_lock, flags);
return 0;
}
static int gem_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
struct tsu_incr incr_spec;
bool neg_adj = false;
u32 word;
u64 adj;
if (scaled_ppm < 0) {
neg_adj = true;
scaled_ppm = -scaled_ppm;
}
/* Adjustment is relative to base frequency */
incr_spec.sub_ns = bp->tsu_incr.sub_ns;
incr_spec.ns = bp->tsu_incr.ns;
/* scaling: unused(8bit) | ns(8bit) | fractions(16bit) */
word = ((u64)incr_spec.ns << GEM_SUBNSINCR_SIZE) + incr_spec.sub_ns;
adj = (u64)scaled_ppm * word;
/* Divide with rounding, equivalent to floating dividing:
* (temp / USEC_PER_SEC) + 0.5
*/
adj += (USEC_PER_SEC >> 1);
adj >>= PPM_FRACTION; /* remove fractions */
adj = div_u64(adj, USEC_PER_SEC);
adj = neg_adj ? (word - adj) : (word + adj);
incr_spec.ns = (adj >> GEM_SUBNSINCR_SIZE)
& ((1 << GEM_NSINCR_SIZE) - 1);
incr_spec.sub_ns = adj & ((1 << GEM_SUBNSINCR_SIZE) - 1);
gem_tsu_incr_set(bp, &incr_spec);
return 0;
}
static int gem_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct macb *bp = container_of(ptp, struct macb, ptp_clock_info);
struct timespec64 now, then = ns_to_timespec64(delta);
u32 adj, sign = 0;
if (delta < 0) {
sign = 1;
delta = -delta;
}
if (delta > TSU_NSEC_MAX_VAL) {
gem_tsu_get_time(&bp->ptp_clock_info, &now, NULL);
now = timespec64_add(now, then);
gem_tsu_set_time(&bp->ptp_clock_info,
(const struct timespec64 *)&now);
} else {
adj = (sign << GEM_ADDSUB_OFFSET) | delta;
gem_writel(bp, TA, adj);
}
return 0;
}
static int gem_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
return -EOPNOTSUPP;
}
static const struct ptp_clock_info gem_ptp_caps_template = {
.owner = THIS_MODULE,
.name = GEM_PTP_TIMER_NAME,
.max_adj = 0,
.n_alarm = 0,
.n_ext_ts = 0,
.n_per_out = 0,
.n_pins = 0,
.pps = 1,
.adjfine = gem_ptp_adjfine,
.adjtime = gem_ptp_adjtime,
.gettimex64 = gem_tsu_get_time,
.settime64 = gem_tsu_set_time,
.enable = gem_ptp_enable,
};
static void gem_ptp_init_timer(struct macb *bp)
{
u32 rem = 0;
u64 adj;
bp->tsu_incr.ns = div_u64_rem(NSEC_PER_SEC, bp->tsu_rate, &rem);
if (rem) {
adj = rem;
adj <<= GEM_SUBNSINCR_SIZE;
bp->tsu_incr.sub_ns = div_u64(adj, bp->tsu_rate);
} else {
bp->tsu_incr.sub_ns = 0;
}
}
static void gem_ptp_init_tsu(struct macb *bp)
{
struct timespec64 ts;
/* 1. get current system time */
ts = ns_to_timespec64(ktime_to_ns(ktime_get_real()));
/* 2. set ptp timer */
gem_tsu_set_time(&bp->ptp_clock_info, &ts);
/* 3. set PTP timer increment value to BASE_INCREMENT */
gem_tsu_incr_set(bp, &bp->tsu_incr);
gem_writel(bp, TA, 0);
}
static void gem_ptp_clear_timer(struct macb *bp)
{
bp->tsu_incr.sub_ns = 0;
bp->tsu_incr.ns = 0;
gem_writel(bp, TISUBN, GEM_BF(SUBNSINCR, 0));
gem_writel(bp, TI, GEM_BF(NSINCR, 0));
gem_writel(bp, TA, 0);
}
static int gem_hw_timestamp(struct macb *bp, u32 dma_desc_ts_1,
u32 dma_desc_ts_2, struct timespec64 *ts)
{
struct timespec64 tsu;
ts->tv_sec = (GEM_BFEXT(DMA_SECH, dma_desc_ts_2) << GEM_DMA_SECL_SIZE) |
GEM_BFEXT(DMA_SECL, dma_desc_ts_1);
ts->tv_nsec = GEM_BFEXT(DMA_NSEC, dma_desc_ts_1);
/* TSU overlapping workaround
* The timestamp only contains lower few bits of seconds,
* so add value from 1588 timer
*/
gem_tsu_get_time(&bp->ptp_clock_info, &tsu, NULL);
ts->tv_sec |= ((~GEM_DMA_SEC_MASK) & tsu.tv_sec);
/* If the top bit is set in the timestamp,
* but not in 1588 timer, it has rolled over,
* so subtract max size
*/
if ((ts->tv_sec & (GEM_DMA_SEC_TOP >> 1)) &&
!(tsu.tv_sec & (GEM_DMA_SEC_TOP >> 1)))
ts->tv_sec -= GEM_DMA_SEC_TOP;
return 0;
}
void gem_ptp_rxstamp(struct macb *bp, struct sk_buff *skb,
struct macb_dma_desc *desc)
{
struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb);
struct macb_dma_desc_ptp *desc_ptp;
struct timespec64 ts;
if (GEM_BFEXT(DMA_RXVALID, desc->addr)) {
desc_ptp = macb_ptp_desc(bp, desc);
/* Unlikely but check */
if (!desc_ptp) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not supported in BD\n");
return;
}
gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
}
}
void gem_ptp_txstamp(struct macb *bp, struct sk_buff *skb,
struct macb_dma_desc *desc)
{
struct skb_shared_hwtstamps shhwtstamps;
struct macb_dma_desc_ptp *desc_ptp;
struct timespec64 ts;
if (!GEM_BFEXT(DMA_TXVALID, desc->ctrl)) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not set in TX BD as expected\n");
return;
}
desc_ptp = macb_ptp_desc(bp, desc);
/* Unlikely but check */
if (!desc_ptp) {
dev_warn_ratelimited(&bp->pdev->dev,
"Timestamp not supported in BD\n");
return;
}
/* ensure ts_1/ts_2 is loaded after ctrl (TX_USED check) */
dma_rmb();
gem_hw_timestamp(bp, desc_ptp->ts_1, desc_ptp->ts_2, &ts);
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_tstamp_tx(skb, &shhwtstamps);
}
void gem_ptp_init(struct net_device *dev)
{
struct macb *bp = netdev_priv(dev);
bp->ptp_clock_info = gem_ptp_caps_template;
/* nominal frequency and maximum adjustment in ppb */
bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp);
bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj();
gem_ptp_init_timer(bp);
bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &dev->dev);
if (IS_ERR(bp->ptp_clock)) {
pr_err("ptp clock register failed: %ld\n",
PTR_ERR(bp->ptp_clock));
bp->ptp_clock = NULL;
return;
} else if (bp->ptp_clock == NULL) {
pr_err("ptp clock register failed\n");
return;
}
spin_lock_init(&bp->tsu_clk_lock);
gem_ptp_init_tsu(bp);
dev_info(&bp->pdev->dev, "%s ptp clock registered.\n",
GEM_PTP_TIMER_NAME);
}
void gem_ptp_remove(struct net_device *ndev)
{
struct macb *bp = netdev_priv(ndev);
if (bp->ptp_clock) {
ptp_clock_unregister(bp->ptp_clock);
bp->ptp_clock = NULL;
}
gem_ptp_clear_timer(bp);
dev_info(&bp->pdev->dev, "%s ptp clock unregistered.\n",
GEM_PTP_TIMER_NAME);
}
static int gem_ptp_set_ts_mode(struct macb *bp,
enum macb_bd_control tx_bd_control,
enum macb_bd_control rx_bd_control)
{
gem_writel(bp, TXBDCTRL, GEM_BF(TXTSMODE, tx_bd_control));
gem_writel(bp, RXBDCTRL, GEM_BF(RXTSMODE, rx_bd_control));
return 0;
}
int gem_get_hwtst(struct net_device *dev,
struct kernel_hwtstamp_config *tstamp_config)
{
struct macb *bp = netdev_priv(dev);
*tstamp_config = bp->tstamp_config;
if (!macb_dma_ptp(bp))
return -EOPNOTSUPP;
return 0;
}
static void gem_ptp_set_one_step_sync(struct macb *bp, u8 enable)
{
u32 reg_val;
reg_val = macb_readl(bp, NCR);
if (enable)
macb_writel(bp, NCR, reg_val | MACB_BIT(OSSMODE));
else
macb_writel(bp, NCR, reg_val & ~MACB_BIT(OSSMODE));
}
int gem_set_hwtst(struct net_device *dev,
struct kernel_hwtstamp_config *tstamp_config,
struct netlink_ext_ack *extack)
{
enum macb_bd_control tx_bd_control = TSTAMP_DISABLED;
enum macb_bd_control rx_bd_control = TSTAMP_DISABLED;
struct macb *bp = netdev_priv(dev);
u32 regval;
if (!macb_dma_ptp(bp))
return -EOPNOTSUPP;
switch (tstamp_config->tx_type) {
case HWTSTAMP_TX_OFF:
break;
case HWTSTAMP_TX_ONESTEP_SYNC:
gem_ptp_set_one_step_sync(bp, 1);
tx_bd_control = TSTAMP_ALL_FRAMES;
break;
case HWTSTAMP_TX_ON:
gem_ptp_set_one_step_sync(bp, 0);
tx_bd_control = TSTAMP_ALL_FRAMES;
break;
default:
return -ERANGE;
}
switch (tstamp_config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
break;
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
break;
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
rx_bd_control = TSTAMP_ALL_PTP_FRAMES;
tstamp_config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
regval = macb_readl(bp, NCR);
macb_writel(bp, NCR, (regval | MACB_BIT(SRTSM)));
break;
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_ALL:
rx_bd_control = TSTAMP_ALL_FRAMES;
tstamp_config->rx_filter = HWTSTAMP_FILTER_ALL;
break;
default:
tstamp_config->rx_filter = HWTSTAMP_FILTER_NONE;
return -ERANGE;
}
bp->tstamp_config = *tstamp_config;
if (gem_ptp_set_ts_mode(bp, tx_bd_control, rx_bd_control) != 0)
return -ERANGE;
return 0;
}