mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-28 06:34:12 +08:00
13d0cecb46
Currently, we allocate a nbytes-sized kernel buffer and copy nbytes from
userspace to that buffer. Later, we use sscanf on this buffer but we don't
ensure that the string is terminated inside the buffer, this can lead to
OOB read when using sscanf. Fix this issue by using memdup_user_nul instead
of memdup_user.
Fixes: 9f30b67475
("bfa: replace 2 kzalloc/copy_from_user by memdup_user")
Signed-off-by: Bui Quang Minh <minhquangbui99@gmail.com>
Link: https://lore.kernel.org/r/20240424-fix-oob-read-v2-3-f1f1b53a10f4@gmail.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
504 lines
12 KiB
C
504 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2005-2014 Brocade Communications Systems, Inc.
|
|
* Copyright (c) 2014- QLogic Corporation.
|
|
* All rights reserved
|
|
* www.qlogic.com
|
|
*
|
|
* Linux driver for QLogic BR-series Fibre Channel Host Bus Adapter.
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/export.h>
|
|
|
|
#include "bfad_drv.h"
|
|
#include "bfad_im.h"
|
|
|
|
/*
|
|
* BFA debufs interface
|
|
*
|
|
* To access the interface, debugfs file system should be mounted
|
|
* if not already mounted using:
|
|
* mount -t debugfs none /sys/kernel/debug
|
|
*
|
|
* BFA Hierarchy:
|
|
* - bfa/pci_dev:<pci_name>
|
|
* where the pci_name corresponds to the one under /sys/bus/pci/drivers/bfa
|
|
*
|
|
* Debugging service available per pci_dev:
|
|
* fwtrc: To collect current firmware trace.
|
|
* drvtrc: To collect current driver trace
|
|
* fwsave: To collect last saved fw trace as a result of firmware crash.
|
|
* regwr: To write one word to chip register
|
|
* regrd: To read one or more words from chip register.
|
|
*/
|
|
|
|
struct bfad_debug_info {
|
|
char *debug_buffer;
|
|
void *i_private;
|
|
int buffer_len;
|
|
};
|
|
|
|
static int
|
|
bfad_debugfs_open_drvtrc(struct inode *inode, struct file *file)
|
|
{
|
|
struct bfad_port_s *port = inode->i_private;
|
|
struct bfad_s *bfad = port->bfad;
|
|
struct bfad_debug_info *debug;
|
|
|
|
debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL);
|
|
if (!debug)
|
|
return -ENOMEM;
|
|
|
|
debug->debug_buffer = (void *) bfad->trcmod;
|
|
debug->buffer_len = sizeof(struct bfa_trc_mod_s);
|
|
|
|
file->private_data = debug;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bfad_debugfs_open_fwtrc(struct inode *inode, struct file *file)
|
|
{
|
|
struct bfad_port_s *port = inode->i_private;
|
|
struct bfad_s *bfad = port->bfad;
|
|
struct bfad_debug_info *fw_debug;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
fw_debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL);
|
|
if (!fw_debug)
|
|
return -ENOMEM;
|
|
|
|
fw_debug->buffer_len = sizeof(struct bfa_trc_mod_s);
|
|
|
|
fw_debug->debug_buffer = vzalloc(fw_debug->buffer_len);
|
|
if (!fw_debug->debug_buffer) {
|
|
kfree(fw_debug);
|
|
printk(KERN_INFO "bfad[%d]: Failed to allocate fwtrc buffer\n",
|
|
bfad->inst_no);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spin_lock_irqsave(&bfad->bfad_lock, flags);
|
|
rc = bfa_ioc_debug_fwtrc(&bfad->bfa.ioc,
|
|
fw_debug->debug_buffer,
|
|
&fw_debug->buffer_len);
|
|
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
|
|
if (rc != BFA_STATUS_OK) {
|
|
vfree(fw_debug->debug_buffer);
|
|
fw_debug->debug_buffer = NULL;
|
|
kfree(fw_debug);
|
|
printk(KERN_INFO "bfad[%d]: Failed to collect fwtrc\n",
|
|
bfad->inst_no);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file->private_data = fw_debug;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bfad_debugfs_open_fwsave(struct inode *inode, struct file *file)
|
|
{
|
|
struct bfad_port_s *port = inode->i_private;
|
|
struct bfad_s *bfad = port->bfad;
|
|
struct bfad_debug_info *fw_debug;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
fw_debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL);
|
|
if (!fw_debug)
|
|
return -ENOMEM;
|
|
|
|
fw_debug->buffer_len = sizeof(struct bfa_trc_mod_s);
|
|
|
|
fw_debug->debug_buffer = vzalloc(fw_debug->buffer_len);
|
|
if (!fw_debug->debug_buffer) {
|
|
kfree(fw_debug);
|
|
printk(KERN_INFO "bfad[%d]: Failed to allocate fwsave buffer\n",
|
|
bfad->inst_no);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spin_lock_irqsave(&bfad->bfad_lock, flags);
|
|
rc = bfa_ioc_debug_fwsave(&bfad->bfa.ioc,
|
|
fw_debug->debug_buffer,
|
|
&fw_debug->buffer_len);
|
|
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
|
|
if (rc != BFA_STATUS_OK) {
|
|
vfree(fw_debug->debug_buffer);
|
|
fw_debug->debug_buffer = NULL;
|
|
kfree(fw_debug);
|
|
printk(KERN_INFO "bfad[%d]: Failed to collect fwsave\n",
|
|
bfad->inst_no);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
file->private_data = fw_debug;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bfad_debugfs_open_reg(struct inode *inode, struct file *file)
|
|
{
|
|
struct bfad_debug_info *reg_debug;
|
|
|
|
reg_debug = kzalloc(sizeof(struct bfad_debug_info), GFP_KERNEL);
|
|
if (!reg_debug)
|
|
return -ENOMEM;
|
|
|
|
reg_debug->i_private = inode->i_private;
|
|
|
|
file->private_data = reg_debug;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Changes the current file position */
|
|
static loff_t
|
|
bfad_debugfs_lseek(struct file *file, loff_t offset, int orig)
|
|
{
|
|
struct bfad_debug_info *debug = file->private_data;
|
|
return fixed_size_llseek(file, offset, orig,
|
|
debug->buffer_len);
|
|
}
|
|
|
|
static ssize_t
|
|
bfad_debugfs_read(struct file *file, char __user *buf,
|
|
size_t nbytes, loff_t *pos)
|
|
{
|
|
struct bfad_debug_info *debug = file->private_data;
|
|
|
|
if (!debug || !debug->debug_buffer)
|
|
return 0;
|
|
|
|
return simple_read_from_buffer(buf, nbytes, pos,
|
|
debug->debug_buffer, debug->buffer_len);
|
|
}
|
|
|
|
#define BFA_REG_CT_ADDRSZ (0x40000)
|
|
#define BFA_REG_CB_ADDRSZ (0x20000)
|
|
#define BFA_REG_ADDRSZ(__ioc) \
|
|
((u32)(bfa_asic_id_ctc(bfa_ioc_devid(__ioc)) ? \
|
|
BFA_REG_CT_ADDRSZ : BFA_REG_CB_ADDRSZ))
|
|
#define BFA_REG_ADDRMSK(__ioc) (BFA_REG_ADDRSZ(__ioc) - 1)
|
|
|
|
static bfa_status_t
|
|
bfad_reg_offset_check(struct bfa_s *bfa, u32 offset, u32 len)
|
|
{
|
|
u8 area;
|
|
|
|
/* check [16:15] */
|
|
area = (offset >> 15) & 0x7;
|
|
if (area == 0) {
|
|
/* PCIe core register */
|
|
if ((offset + (len<<2)) > 0x8000) /* 8k dwords or 32KB */
|
|
return BFA_STATUS_EINVAL;
|
|
} else if (area == 0x1) {
|
|
/* CB 32 KB memory page */
|
|
if ((offset + (len<<2)) > 0x10000) /* 8k dwords or 32KB */
|
|
return BFA_STATUS_EINVAL;
|
|
} else {
|
|
/* CB register space 64KB */
|
|
if ((offset + (len<<2)) > BFA_REG_ADDRMSK(&bfa->ioc))
|
|
return BFA_STATUS_EINVAL;
|
|
}
|
|
return BFA_STATUS_OK;
|
|
}
|
|
|
|
static ssize_t
|
|
bfad_debugfs_read_regrd(struct file *file, char __user *buf,
|
|
size_t nbytes, loff_t *pos)
|
|
{
|
|
struct bfad_debug_info *regrd_debug = file->private_data;
|
|
struct bfad_port_s *port = (struct bfad_port_s *)regrd_debug->i_private;
|
|
struct bfad_s *bfad = port->bfad;
|
|
ssize_t rc;
|
|
|
|
if (!bfad->regdata)
|
|
return 0;
|
|
|
|
rc = simple_read_from_buffer(buf, nbytes, pos,
|
|
bfad->regdata, bfad->reglen);
|
|
|
|
if ((*pos + nbytes) >= bfad->reglen) {
|
|
kfree(bfad->regdata);
|
|
bfad->regdata = NULL;
|
|
bfad->reglen = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t
|
|
bfad_debugfs_write_regrd(struct file *file, const char __user *buf,
|
|
size_t nbytes, loff_t *ppos)
|
|
{
|
|
struct bfad_debug_info *regrd_debug = file->private_data;
|
|
struct bfad_port_s *port = (struct bfad_port_s *)regrd_debug->i_private;
|
|
struct bfad_s *bfad = port->bfad;
|
|
struct bfa_s *bfa = &bfad->bfa;
|
|
struct bfa_ioc_s *ioc = &bfa->ioc;
|
|
int addr, rc, i;
|
|
u32 len;
|
|
u32 *regbuf;
|
|
void __iomem *rb, *reg_addr;
|
|
unsigned long flags;
|
|
void *kern_buf;
|
|
|
|
kern_buf = memdup_user_nul(buf, nbytes);
|
|
if (IS_ERR(kern_buf))
|
|
return PTR_ERR(kern_buf);
|
|
|
|
rc = sscanf(kern_buf, "%x:%x", &addr, &len);
|
|
if (rc < 2 || len > (UINT_MAX >> 2)) {
|
|
printk(KERN_INFO
|
|
"bfad[%d]: %s failed to read user buf\n",
|
|
bfad->inst_no, __func__);
|
|
kfree(kern_buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kfree(kern_buf);
|
|
kfree(bfad->regdata);
|
|
bfad->regdata = NULL;
|
|
bfad->reglen = 0;
|
|
|
|
bfad->regdata = kzalloc(len << 2, GFP_KERNEL);
|
|
if (!bfad->regdata) {
|
|
printk(KERN_INFO "bfad[%d]: Failed to allocate regrd buffer\n",
|
|
bfad->inst_no);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bfad->reglen = len << 2;
|
|
rb = bfa_ioc_bar0(ioc);
|
|
addr &= BFA_REG_ADDRMSK(ioc);
|
|
|
|
/* offset and len sanity check */
|
|
rc = bfad_reg_offset_check(bfa, addr, len);
|
|
if (rc) {
|
|
printk(KERN_INFO "bfad[%d]: Failed reg offset check\n",
|
|
bfad->inst_no);
|
|
kfree(bfad->regdata);
|
|
bfad->regdata = NULL;
|
|
bfad->reglen = 0;
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg_addr = rb + addr;
|
|
regbuf = (u32 *)bfad->regdata;
|
|
spin_lock_irqsave(&bfad->bfad_lock, flags);
|
|
for (i = 0; i < len; i++) {
|
|
*regbuf = readl(reg_addr);
|
|
regbuf++;
|
|
reg_addr += sizeof(u32);
|
|
}
|
|
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static ssize_t
|
|
bfad_debugfs_write_regwr(struct file *file, const char __user *buf,
|
|
size_t nbytes, loff_t *ppos)
|
|
{
|
|
struct bfad_debug_info *debug = file->private_data;
|
|
struct bfad_port_s *port = (struct bfad_port_s *)debug->i_private;
|
|
struct bfad_s *bfad = port->bfad;
|
|
struct bfa_s *bfa = &bfad->bfa;
|
|
struct bfa_ioc_s *ioc = &bfa->ioc;
|
|
int addr, val, rc;
|
|
void __iomem *reg_addr;
|
|
unsigned long flags;
|
|
void *kern_buf;
|
|
|
|
kern_buf = memdup_user_nul(buf, nbytes);
|
|
if (IS_ERR(kern_buf))
|
|
return PTR_ERR(kern_buf);
|
|
|
|
rc = sscanf(kern_buf, "%x:%x", &addr, &val);
|
|
if (rc < 2) {
|
|
printk(KERN_INFO
|
|
"bfad[%d]: %s failed to read user buf\n",
|
|
bfad->inst_no, __func__);
|
|
kfree(kern_buf);
|
|
return -EINVAL;
|
|
}
|
|
kfree(kern_buf);
|
|
|
|
addr &= BFA_REG_ADDRMSK(ioc); /* offset only 17 bit and word align */
|
|
|
|
/* offset and len sanity check */
|
|
rc = bfad_reg_offset_check(bfa, addr, 1);
|
|
if (rc) {
|
|
printk(KERN_INFO
|
|
"bfad[%d]: Failed reg offset check\n",
|
|
bfad->inst_no);
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg_addr = (bfa_ioc_bar0(ioc)) + addr;
|
|
spin_lock_irqsave(&bfad->bfad_lock, flags);
|
|
writel(val, reg_addr);
|
|
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static int
|
|
bfad_debugfs_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct bfad_debug_info *debug = file->private_data;
|
|
|
|
if (!debug)
|
|
return 0;
|
|
|
|
file->private_data = NULL;
|
|
kfree(debug);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bfad_debugfs_release_fwtrc(struct inode *inode, struct file *file)
|
|
{
|
|
struct bfad_debug_info *fw_debug = file->private_data;
|
|
|
|
if (!fw_debug)
|
|
return 0;
|
|
|
|
vfree(fw_debug->debug_buffer);
|
|
|
|
file->private_data = NULL;
|
|
kfree(fw_debug);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations bfad_debugfs_op_drvtrc = {
|
|
.owner = THIS_MODULE,
|
|
.open = bfad_debugfs_open_drvtrc,
|
|
.llseek = bfad_debugfs_lseek,
|
|
.read = bfad_debugfs_read,
|
|
.release = bfad_debugfs_release,
|
|
};
|
|
|
|
static const struct file_operations bfad_debugfs_op_fwtrc = {
|
|
.owner = THIS_MODULE,
|
|
.open = bfad_debugfs_open_fwtrc,
|
|
.llseek = bfad_debugfs_lseek,
|
|
.read = bfad_debugfs_read,
|
|
.release = bfad_debugfs_release_fwtrc,
|
|
};
|
|
|
|
static const struct file_operations bfad_debugfs_op_fwsave = {
|
|
.owner = THIS_MODULE,
|
|
.open = bfad_debugfs_open_fwsave,
|
|
.llseek = bfad_debugfs_lseek,
|
|
.read = bfad_debugfs_read,
|
|
.release = bfad_debugfs_release_fwtrc,
|
|
};
|
|
|
|
static const struct file_operations bfad_debugfs_op_regrd = {
|
|
.owner = THIS_MODULE,
|
|
.open = bfad_debugfs_open_reg,
|
|
.llseek = bfad_debugfs_lseek,
|
|
.read = bfad_debugfs_read_regrd,
|
|
.write = bfad_debugfs_write_regrd,
|
|
.release = bfad_debugfs_release,
|
|
};
|
|
|
|
static const struct file_operations bfad_debugfs_op_regwr = {
|
|
.owner = THIS_MODULE,
|
|
.open = bfad_debugfs_open_reg,
|
|
.llseek = bfad_debugfs_lseek,
|
|
.write = bfad_debugfs_write_regwr,
|
|
.release = bfad_debugfs_release,
|
|
};
|
|
|
|
struct bfad_debugfs_entry {
|
|
const char *name;
|
|
umode_t mode;
|
|
const struct file_operations *fops;
|
|
};
|
|
|
|
static const struct bfad_debugfs_entry bfad_debugfs_files[] = {
|
|
{ "drvtrc", S_IFREG|S_IRUGO, &bfad_debugfs_op_drvtrc, },
|
|
{ "fwtrc", S_IFREG|S_IRUGO, &bfad_debugfs_op_fwtrc, },
|
|
{ "fwsave", S_IFREG|S_IRUGO, &bfad_debugfs_op_fwsave, },
|
|
{ "regrd", S_IFREG|S_IRUGO|S_IWUSR, &bfad_debugfs_op_regrd, },
|
|
{ "regwr", S_IFREG|S_IWUSR, &bfad_debugfs_op_regwr, },
|
|
};
|
|
|
|
static struct dentry *bfa_debugfs_root;
|
|
static atomic_t bfa_debugfs_port_count;
|
|
|
|
inline void
|
|
bfad_debugfs_init(struct bfad_port_s *port)
|
|
{
|
|
struct bfad_s *bfad = port->bfad;
|
|
const struct bfad_debugfs_entry *file;
|
|
char name[64];
|
|
int i;
|
|
|
|
if (!bfa_debugfs_enable)
|
|
return;
|
|
|
|
/* Setup the BFA debugfs root directory*/
|
|
if (!bfa_debugfs_root) {
|
|
bfa_debugfs_root = debugfs_create_dir("bfa", NULL);
|
|
atomic_set(&bfa_debugfs_port_count, 0);
|
|
}
|
|
|
|
/* Setup the pci_dev debugfs directory for the port */
|
|
snprintf(name, sizeof(name), "pci_dev:%s", bfad->pci_name);
|
|
if (!port->port_debugfs_root) {
|
|
port->port_debugfs_root =
|
|
debugfs_create_dir(name, bfa_debugfs_root);
|
|
|
|
atomic_inc(&bfa_debugfs_port_count);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bfad_debugfs_files); i++) {
|
|
file = &bfad_debugfs_files[i];
|
|
bfad->bfad_dentry_files[i] =
|
|
debugfs_create_file(file->name,
|
|
file->mode,
|
|
port->port_debugfs_root,
|
|
port,
|
|
file->fops);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
inline void
|
|
bfad_debugfs_exit(struct bfad_port_s *port)
|
|
{
|
|
struct bfad_s *bfad = port->bfad;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bfad_debugfs_files); i++) {
|
|
if (bfad->bfad_dentry_files[i]) {
|
|
debugfs_remove(bfad->bfad_dentry_files[i]);
|
|
bfad->bfad_dentry_files[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/* Remove the pci_dev debugfs directory for the port */
|
|
if (port->port_debugfs_root) {
|
|
debugfs_remove(port->port_debugfs_root);
|
|
port->port_debugfs_root = NULL;
|
|
atomic_dec(&bfa_debugfs_port_count);
|
|
}
|
|
|
|
/* Remove the BFA debugfs root directory */
|
|
if (atomic_read(&bfa_debugfs_port_count) == 0) {
|
|
debugfs_remove(bfa_debugfs_root);
|
|
bfa_debugfs_root = NULL;
|
|
}
|
|
}
|