[RFC] ipack: ipoctal: fix use-after-free on remove

From: Pei Xiao

Date: Tue Jun 23 2026 - 03:51:15 EST


A use-after-free occurs when the device is removed while a tty
session is still active. The remove callback frees the ipoctal
structure via kfree() while ipoctal_write_tty() and other tty ops
may still access it.

Fix this by introducing kref-based lifetime management for the
ipoctal structure. A kref is taken in ipoctal_install() when a tty is
initialized, and released in ipoctal_cleanup() when the tty is finally
destroyed. The remove callback replaces direct kfree() with kref_put(),
ensuring the memory is only freed after all tty references have been
released.

Reported-by: Shuangpeng Bai <shuangpeng.kernel@xxxxxxxxx>
Closes: https://lore.kernel.org/lkml/178144969601.60470.1257088106279546587@xxxxxxxxx/
Fixes: 05e5027efc9c ("Staging: ipack: move out of staging")
Signed-off-by: Pei Xiao <xiaopei01@xxxxxxxxxx>
---
drivers/ipack/devices/ipoctal.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c
index 1bbefc6de708..079bda93ad5a 100644
--- a/drivers/ipack/devices/ipoctal.c
+++ b/drivers/ipack/devices/ipoctal.c
@@ -10,6 +10,7 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/serial.h>
@@ -25,6 +26,8 @@

static const struct tty_operations ipoctal_fops;

+static void ipoctal_release(struct kref *kref);
+
struct ipoctal_channel {
struct ipoctal_stats stats;
unsigned int nb_bytes;
@@ -49,6 +52,7 @@ struct ipoctal {
struct tty_driver *tty_drv;
u8 __iomem *mem8_space;
u8 __iomem *int_space;
+ struct kref kref;
};

static inline struct ipoctal *chan_to_ipoctal(struct ipoctal_channel *chan,
@@ -95,6 +99,7 @@ static int ipoctal_install(struct tty_driver *driver, struct tty_struct *tty)
if (res)
goto err_put_carrier;

+ kref_get(&ipoctal->kref);
tty->driver_data = channel;

return 0;
@@ -666,6 +671,7 @@ static void ipoctal_cleanup(struct tty_struct *tty)

/* release the carrier driver */
ipack_put_carrier(ipoctal->dev);
+ kref_put(&ipoctal->kref, ipoctal_release);
}

static const struct tty_operations ipoctal_fops = {
@@ -683,6 +689,13 @@ static const struct tty_operations ipoctal_fops = {
.cleanup = ipoctal_cleanup,
};

+static void ipoctal_release(struct kref *kref)
+{
+ struct ipoctal *ipoctal = container_of(kref, struct ipoctal, kref);
+
+ kfree(ipoctal);
+}
+
static int ipoctal_probe(struct ipack_device *dev)
{
int res;
@@ -692,6 +705,8 @@ static int ipoctal_probe(struct ipack_device *dev)
if (ipoctal == NULL)
return -ENOMEM;

+ kref_init(&ipoctal->kref);
+
ipoctal->dev = dev;
res = ipoctal_inst_slot(ipoctal, dev->bus->bus_nr, dev->slot);
if (res)
@@ -701,7 +716,7 @@ static int ipoctal_probe(struct ipack_device *dev)
return 0;

out_uninst:
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
return res;
}

@@ -725,7 +740,7 @@ static void __ipoctal_remove(struct ipoctal *ipoctal)
tty_unregister_driver(ipoctal->tty_drv);
kfree(ipoctal->tty_drv->name);
tty_driver_kref_put(ipoctal->tty_drv);
- kfree(ipoctal);
+ kref_put(&ipoctal->kref, ipoctal_release);
}

static void ipoctal_remove(struct ipack_device *idev)
--
2.25.1