linux
NULL pointer deference due to invalid cast in x86 NUMA
In x86-32bit NUMA, the map from PCI bus number to NUMA node is an unsigned char array. Invalid entries are marked with (unsigned char)-1, which is (unsigned char)255. As a result, invalid entries are interpreted as NUMA node (int)255. For instance, local_cpus_show() will use this node number to index the node_to_cpumask_map array, whose entries are initialized for valid nodes and otherwise left to NULL. If we get such an invalud NUMA node 255, and it turns out that 255 >= nr_node_ids, then node_to_cpumask_map[255] will be NULL, and we get a NULL pointer dereference when local_cpus_show() tries to print the CPU mask.
Bug fixed by commit 76baeebf7df
| Type | NullDereference |
| Config | "X86_32 && NUMA && PCI" (3rd degree) |
| C-features | FunctionPointers, Structs |
| Fix-in | code |
| Location | arch/x86/pci |
#ifdef CONFIG_X86_32
#undef CONFIG_X86_64
#endif
#include <stdio.h>
/* #define NR_CPUS 64 */
typedef unsigned long cpumask_t;
cpumask_t cpu_none_mask = 0;
cpumask_t cpu_online_mask = 0;
#ifdef CONFIG_NUMA
#define NODES_SHIFT 8
#define MAX_NUMNODES (1 << NODES_SHIFT)
int nr_node_ids;
cpumask_t* node_to_cpumask_map[MAX_NUMNODES];
#else
#define nr_node_ids 1
#endif
#ifdef CONFIG_NUMA
void setup_node_to_cpumask_map(void)
{
unsigned int node;
/* allocate the map */
for (node = 0; node < nr_node_ids; node++)
node_to_cpumask_map[node] = &cpu_online_mask;
}
#ifdef CONFIG_DEBUG_PER_CPU_MAPS
const cpumask_t *cpumask_of_node(int node)
{
if (node >= nr_node_ids) {
fprintf(stderr, "cpumask_of_node(%d): node > nr_node_ids(%d)\n",
node, nr_node_ids);
return cpu_none_mask;
}
if (node_to_cpumask_map[node] == NULL) {
fprintf(stderr, "cpumask_of_node(%d): no node_to_cpumask_map!\n", node);
return cpu_online_mask;
}
return node_to_cpumask_map[node];
}
#else
/* Returns a pointer to the cpumask of CPUs on Node 'node'. */
static inline const cpumask_t *cpumask_of_node(int node)
{
return node_to_cpumask_map[node];
}
#endif /* CONFIG_DEBUG_PER_CPU_MAPS */
#else /* !CONFIG_NUMA */
static inline const cpumask_t *cpumask_of_node(int node)
{
return &cpu_online_mask;
}
static inline void setup_node_to_cpumask_map(void) { }
#endif
#ifdef CONFIG_PCI
#ifdef CONFIG_NUMA
#define BUS_NR 256
#ifdef CONFIG_X86_64
static int mp_bus_to_node[BUS_NR] = {
[0 ... BUS_NR - 1] = -1
};
int get_mp_bus_to_node(int busnum)
{
int node = -1;
if (busnum < 0 || busnum > (BUS_NR - 1))
return node;
node = mp_bus_to_node[busnum];
return node;
}
#else /* CONFIG_X86_32 */
static unsigned char mp_bus_to_node[BUS_NR] = {
[0 ... BUS_NR - 1] = -1
};
int get_mp_bus_to_node(int busnum)
{
int node;
if (busnum < 0 || busnum > (BUS_NR - 1))
return 0;
node = mp_bus_to_node[busnum];
return node;
}
#endif /* CONFIG_X86_32 */
#else
static inline int get_mp_bus_to_node(int busnum)
{
return 0;
}
#endif /* CONFIG_NUMA */
#ifdef CONFIG_NUMA
static const cpumask_t *
cpumask_of_pcibus(int node)
{
return (node == -1) ? &cpu_online_mask
: cpumask_of_node(node);
}
#endif
static int local_cpus_show(int node)
{
const cpumask_t *mask;
int len = 1;
#ifdef CONFIG_NUMA
mask = cpumask_of_pcibus(node);
#else
mask = cpumask_of_node(node);
#endif
printf("mask: %ld\n", *mask);
return len;
}
static int dev_attr_show(int node)
{
int ret;
ret = local_cpus_show(node);
return ret;
}
int pcibios_scan_root()
{
return get_mp_bus_to_node(0);
}
#endif /* CONFIG_PCI */
int main(int argc, char** argv)
{
#ifdef CONFIG_NUMA
nr_node_ids = rand() % (MAX_NUMNODES-1);
#endif
setup_node_to_cpumask_map();
#ifdef CONFIG_PCI
int node = pcibios_scan_root();
dev_attr_show(node);
#endif
return 0;
}
diff --git a/simple/76baeeb.c b/simple/76baeeb.c
--- a/simple/76baeeb.c
+++ b/simple/76baeeb.c
@@ -91,7 +91,7 @@
#else /* CONFIG_X86_32 */
-static unsigned char mp_bus_to_node[BUS_NR] = {
+static int mp_bus_to_node[BUS_NR] = {
[0 ... BUS_NR - 1] = -1
};
#ifdef CONFIG_X86_32
#undef CONFIG_X86_64
#endif
#include <stdio.h>
/* #define NR_CPUS 64 */
typedef unsigned long cpumask_t;
cpumask_t cpu_none_mask = 0;
cpumask_t cpu_online_mask = 0;
#ifdef CONFIG_NUMA
#define NODES_SHIFT 8
#define MAX_NUMNODES (1 << NODES_SHIFT)
int nr_node_ids;
cpumask_t* node_to_cpumask_map[MAX_NUMNODES];
#else
#define nr_node_ids 1
#endif
int main(int argc, char** argv)
{
#ifdef CONFIG_NUMA
nr_node_ids = rand() % (MAX_NUMNODES-1);
#endif
// setup_node_to_cpumask_map();
unsigned int node;
/* allocate the map */
for (node = 0; node < nr_node_ids; node++)
node_to_cpumask_map[node] = &cpu_online_mask;
#ifdef CONFIG_PCI
// int node = pcibios_scan_root();
#ifdef CONFIG_NUMA
#define BUS_NR 256
#ifdef CONFIG_X86_64
static int mp_bus_to_node[BUS_NR] = {
[0 ... BUS_NR - 1] = -1
};
int node = -1;
if (busnum < 0 || busnum > (BUS_NR - 1))
return node;
node = mp_bus_to_node[busnum];
#else /* CONFIG_X86_32 */
static unsigned char mp_bus_to_node[BUS_NR] = {
[0 ... BUS_NR - 1] = -1
};
if (busnum < 0 || busnum > (BUS_NR - 1))
return 0;
node = mp_bus_to_node[busnum];
#endif
#endif
// dev_attr_show(node);
const cpumask_t *mask;
int len = 1;
#ifdef CONFIG_NUMA
mask = (node == -1) ? &cpu_online_mask
:
//cpumask_of_node(node);
#ifdef CONFIG_DEBUG_PER_CPU_MAPS
if (node >= nr_node_ids) {
fprintf(stderr, "cpumask_of_node(%d): node > nr_node_ids(%d)\n",node, nr_node_ids);
cpu_none_mask;
}
if (node_to_cpumask_map[node] == NULL) {
fprintf(stderr, "cpumask_of_node(%d): no node_to_cpumask_map!\n", node);
cpu_online_mask;
}
node_to_cpumask_map[node];
#else
node_to_cpumask_map[node];
#endif
#else
mask = &cpu_online_mask;
#endif
printf("mask: %ld\n", *mask);
#endif
return 0;
}
. call arch/x86/pci/common.c:381:__devinit pcibios_scan_root() // called by some pci_*_init() function . 403: sd->node = get_mp_bus_to_node(busnum); .. [NUMA && X86_32] call arch/x86/pci/common.c:659:get_mp_bus_to_node() .. node = mp_bus_to_node[busnum]; // `node' has type int but `mp_bus_to_node' is an unsigned char array // the default entry value is (unsigned char)-1, i.e. (unsigned char)255 // thus we could get an (int)255 as a node value . call drivers/base/core.c:68:dev_attr_show() . 75: if (dev_attr->show) . 76: ret = dev_attr->show(dev, dev_attr, buf); .. dyn-call drivers/pci/pci-sysfs.c:71:local_cpus_show() .. 77: mask = cpumask_of_pcibus(to_pci_dev(dev)->bus); ... [NUMA] call arch/x86/include/asm/pci.h:144:cpumask_of_pcibus() ... 148: node = __pcibus_to_node(bus); .... [NUMA] call arch/x86/include/asm/pci.h:136:__pcibus_to_node() .... 140: return sd->node; ... 150: cpumask_of_node(node); // we take this branch because node equals to (int)255, not to (int)-1 .... [!DEBUG_PER_CPU_MAPS] call arch/x86/include/asm/topology.h:95:cpumask_of_node() .... 97: return node_to_cpumask_map[node]; // node_to_cpumask_map is a global array of pointers of MAX_NUMNODES entries. // entries are initialized to NULL according to ANSI C standard // thus if node >= nr_node_ids, mask will be NULL. .. 78: len = cpumask_scnprintf(buf, PAGE_SIZE-2, mask); // back to local_cpus_show() ... mask == NULL ! ... call include/linux/cpumask.h:943:cpumask_scnprintf() ... ERROR 946: return bitmap_scnprintf(buf, len, cpumask_bits(srcp), nr_cpumask_bits); // cpumask_bits(srcp) is defined as srcp->bits, and srcp is an alias for mask which is NULL