From nobody@FreeBSD.org  Tue Jul 13 16:03:27 2010
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34])
	by hub.freebsd.org (Postfix) with ESMTP id A55E31065672
	for <freebsd-gnats-submit@FreeBSD.org>; Tue, 13 Jul 2010 16:03:27 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (www.freebsd.org [IPv6:2001:4f8:fff6::21])
	by mx1.freebsd.org (Postfix) with ESMTP id 79AD78FC08
	for <freebsd-gnats-submit@FreeBSD.org>; Tue, 13 Jul 2010 16:03:27 +0000 (UTC)
Received: from www.freebsd.org (localhost [127.0.0.1])
	by www.freebsd.org (8.14.3/8.14.3) with ESMTP id o6DG3RHM007227
	for <freebsd-gnats-submit@FreeBSD.org>; Tue, 13 Jul 2010 16:03:27 GMT
	(envelope-from nobody@www.freebsd.org)
Received: (from nobody@localhost)
	by www.freebsd.org (8.14.3/8.14.3/Submit) id o6DG3RIg007226;
	Tue, 13 Jul 2010 16:03:27 GMT
	(envelope-from nobody)
Message-Id: <201007131603.o6DG3RIg007226@www.freebsd.org>
Date: Tue, 13 Jul 2010 16:03:27 GMT
From: Spencer Minear <spencer_minear@mcafee.com>
To: freebsd-gnats-submit@FreeBSD.org
Subject: Buffer overrun in the impi driver while processing smbios date
X-Send-Pr-Version: www-3.1
X-GNATS-Notify:

>Number:         148546
>Category:       kern
>Synopsis:       [ipmi] Buffer overrun in the impi driver while processing smbios date
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    jhb
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Jul 13 16:10:05 UTC 2010
>Closed-Date:    Mon Aug 09 20:09:40 UTC 2010
>Last-Modified:  Mon Aug 09 20:09:40 UTC 2010
>Originator:     Spencer Minear
>Release:        7.2
>Organization:
Mcafee
>Environment:
Observed in our modified version of 7.2.  But the bad code appears to be present in a wide range of releases
>Description:
The smbios_run_table function in ipmi_smbios.c allocates a local variable named table that can contain 20 entries.  It then calls the get_strings function which is also in ipmi_smbios.c and provides the address of the table as a parameter to the get_strings functions.

As the get_strings function is walking the list of strings it saves a pointer to the string in the table with out any consideration to how many pointers it adds to the table.

The obvious error is that IF the bios happens to have a SMBIOS entry that contains more than 19 strings, we need room for the terminal NULL pointer which is also added by the get_strings function, it will overflow the table.

I've found NO indication in the specifications that 20 is a fixed limit on the number of strings.  We have observed that with the latest BIOS update from Dell and use of one of their new large systems, that the buffer will overrun and can lead to panic because the buffer overrun can cause a panic during system boot when the ipmi_smbios_identfy function is called as the bus_generic_probe fuction is walking the list of drivers.

>How-To-Repeat:
We found the problem using the 2.1.9 version of the BIOS from Dell running on a Dell T610 MLK system.  The problem smbios entry is one that provides information on the CPU and DIM sockets on the mother board.  This system has 2 CPU sockets and a total of 18 DIM sockets, which leads to 20 strings giving names of all of them.

We see a panic when using a i386 kernel with no debug flags set.  In this case the NULL happens to overwrite the saved EBX register from the bus_generic_attach function.  We have not observed a problem with a amd64 bit system, but clearly the error exists but what ever is being overwritten is not as critical as in the i368

>Fix:
The fix that we are making involves first making the table bigger.  We also are adding a parameter to the get_strings function to tell it the number of allowed entries in the table.  The get_strings logic counts entries and IF it sees that it will overflow the table it will burp an overflow message to the console and NOT overflow the table.  Worst case one or more some strings will not be referenced.  But at least it won't panic the kernel during boot.

>Release-Note:
>Audit-Trail:

From: John Baldwin <jhb@FreeBSD.org>
To: bug-followup@FreeBSD.org, spencer_minear@mcafee.com
Cc:  
Subject: Re: kern/148546: [ipmi] Buffer overrun in the impi driver while processing
 smbios date
Date: Tue, 13 Jul 2010 15:06:02 -0400

 Hmm, the smbios table parser in ipmi_smbios.c is a bit broken. :(  I 
 think it was derived from a more generic parser.  At some point it might 
 be useful to write a more generic smbios table parser that this code 
 could use, but the simplest fix might be to just simplify this code to 
 be more IPMI specific.  For example, the IPMI table entry doesn't use 
 the strings at all, so the table of strings could just be dropped.  We 
 could also remove the dispatch table and instead check the table entry 
 type in the the smbios_t38_proc_info() function.  This is more like what 
 other places in the kernel do when walking tables e.g. the MADT or MP Table.
 
 -- 
 John Baldwin

From: John Baldwin <jhb@freebsd.org>
To: bug-followup@freebsd.org
Cc: spencer_minear@mcafee.com
Subject: Re: kern/148546: [ipmi] Buffer overrun in the impi driver while processing smbios date
Date: Tue, 13 Jul 2010 15:48:10 -0400

 On Tuesday, July 13, 2010 3:06:02 pm John Baldwin wrote:
 > Hmm, the smbios table parser in ipmi_smbios.c is a bit broken. :(  I 
 > think it was derived from a more generic parser.  At some point it might 
 > be useful to write a more generic smbios table parser that this code 
 > could use, but the simplest fix might be to just simplify this code to 
 > be more IPMI specific.  For example, the IPMI table entry doesn't use 
 > the strings at all, so the table of strings could just be dropped.  We 
 > could also remove the dispatch table and instead check the table entry 
 > type in the the smbios_t38_proc_info() function.  This is more like what 
 > other places in the kernel do when walking tables e.g. the MADT or MP Table.
 
 Here is a possible implementation of this.  I have compiled it but have not 
 yet run tested it:
 
 Index: ipmi_smbios.c
 ===================================================================
 --- ipmi_smbios.c	(revision 209948)
 +++ ipmi_smbios.c	(working copy)
 @@ -52,7 +52,7 @@
  #define	pmap_unmapbios		pmap_unmapdev
  #endif
  
 -struct smbios_table_entry {
 +struct smbios_table {
  	uint8_t		anchor_string[4];
  	uint8_t		checksum;
  	uint8_t		length;
 @@ -108,27 +108,30 @@
  #define	SMBIOS_LEN		4
  #define	SMBIOS_SIG		"_SM_"
  
 -typedef void (*dispatchproc_t)(uint8_t *p, char **table,
 -    struct ipmi_get_info *info);
 +typedef void (*smbios_callback_t)(struct structure_header *, void *);
  
  static struct ipmi_get_info ipmi_info;
  static int ipmi_probed;
  static struct mtx ipmi_info_mtx;
  MTX_SYSINIT(ipmi_info, &ipmi_info_mtx, "ipmi info", MTX_DEF);
  
 -static char	*get_strings(char *, char **);
  static void	ipmi_smbios_probe(struct ipmi_get_info *);
 -static int	smbios_cksum	(struct smbios_table_entry *);
 -static void	smbios_run_table(uint8_t *, int, dispatchproc_t *,
 -		    struct ipmi_get_info *);
 -static void	smbios_t38_proc_info(uint8_t *, char **,
 -		    struct ipmi_get_info *);
 +static int	smbios_cksum(struct smbios_table *);
 +static void	smbios_walk_table(uint8_t *, int, smbios_callback_t,
 +		    void *);
 +static void	smbios_ipmi_info(struct structure_header *, void *);
  
  static void
 -smbios_t38_proc_info(uint8_t *p, char **table, struct ipmi_get_info *info)
 +smbios_ipmi_info(struct structure_header *h, void *arg)
  {
 -	struct ipmi_entry *s = (struct ipmi_entry *)p;
 +	struct ipmi_get_info *info;
 +	struct ipmi_entry *s;
  
 +	if (h->type != 38 || h->length <
 +	    offsetof(struct ipmi_entry, interrupt_number))
 +		return;
 +	s = (struct ipmi_entry *)h;
 +	info = arg;
  	bzero(info, sizeof(struct ipmi_get_info));
  	switch (s->interface_type) {
  	case KCS_MODE:
 @@ -172,44 +175,28 @@
  	info->iface_type = s->interface_type;
  }
  
 -static char *
 -get_strings(char *p, char **table)
 -{
 -	/* Scan for strings, stoping at a single null byte */
 -	while (*p != 0) {
 -		*table++ = p;
 -		p += strlen(p) + 1;
 -	}
 -	*table = 0;
 -
 -	/* Skip past terminating null byte */
 -	return (p + 1);
 -}
 -
 -
  static void
 -smbios_run_table(uint8_t *p, int entries, dispatchproc_t *dispatchstatus,
 -    struct ipmi_get_info *info)
 +smbios_walk_table(uint8_t *p, int entries, smbios_callback_t cb, void *arg)
  {
  	struct structure_header *s;
 -	char *table[20];
 -	uint8_t *nextp;
  
 -	while(entries--) {
 -		s = (struct structure_header *) p;
 -		nextp = get_strings(p + s->length, table);
 +	while (entries--) {
 +		s = (struct structure_header *)p;
 +		cb(s, arg);
  
  		/*
 -		 * No strings still has a double-null at the end,
 -		 * skip over it
 +		 * Look for a double-nul after the end of the
 +		 * formatted area of this structure.
  		 */
 -		if (table[0] == 0)
 -			nextp++;
 +		p += s->length;
 +		while (p[0] != 0 && p[1] != 0)
 +			p++;
  
 -		if (dispatchstatus[*p]) {
 -			(dispatchstatus[*p])(p, table, info);
 -		}
 -		p = nextp;
 +		/*
 +		 * Skip over the double-nul to the start of the next
 +		 * structure.
 +		 */
 +		p += 2;
  	}
  }
  
 @@ -221,8 +208,7 @@
  static void
  ipmi_smbios_probe(struct ipmi_get_info *info)
  {
 -	dispatchproc_t dispatch_smbios_ipmi[256];
 -	struct smbios_table_entry *header;
 +	struct smbios_table *header;
  	void *table;
  	u_int32_t addr;
  
 @@ -239,9 +225,9 @@
  	 * length and then map it a second time with the actual length so
  	 * we can verify the checksum.
  	 */
 -	header = pmap_mapbios(addr, sizeof(struct smbios_table_entry));
 +	header = pmap_mapbios(addr, sizeof(struct smbios_table));
  	table = pmap_mapbios(addr, header->length);
 -	pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_table_entry));
 +	pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_table));
  	header = table;
  	if (smbios_cksum(header) != 0) {
  		pmap_unmapbios((vm_offset_t)header, header->length);
 @@ -251,9 +237,7 @@
  	/* Now map the actual table and walk it looking for an IPMI entry. */
  	table = pmap_mapbios(header->structure_table_address,
  	    header->structure_table_length);
 -	bzero((void *)dispatch_smbios_ipmi, sizeof(dispatch_smbios_ipmi));
 -	dispatch_smbios_ipmi[38] = (void *)smbios_t38_proc_info;
 -	smbios_run_table(table, header->number_structures, dispatch_smbios_ipmi,
 +	smbios_walk_table(table, header->number_structures, smbios_ipmi_info,
  	    info);
  
  	/* Unmap everything. */
 @@ -298,7 +282,7 @@
  }
  
  static int
 -smbios_cksum (struct smbios_table_entry *e)
 +smbios_cksum(struct smbios_table *e)
  {
  	u_int8_t *ptr;
  	u_int8_t cksum;
 
 -- 
 John Baldwin

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: kern/148546: commit references a PR
Date: Wed, 14 Jul 2010 18:08:33 +0000 (UTC)

 Author: jhb
 Date: Wed Jul 14 18:06:21 2010
 New Revision: 210066
 URL: http://svn.freebsd.org/changeset/base/210066
 
 Log:
   Rework the SMBIOS table walker to make it operate like other table walkers
   and remove a buffer overflow:
   - Remove the array of per-type dispatch functions.  Instead, pass each
     structure to a single callback.  The callback should check the type of
     each table entry to take appropriate action.  This matches the behavior
     of other table walkers such as for the MP Table and MADT.
   - Don't attempt to save an array of string pointers for each structure
     entry.  Instead, just skip the strings.  If this code is reused to
     provide a generic SMBIOS table walker in the future we could provide
     a method that looks up a specific string N for a given structure record
     instead of pre-populating an array of pointers.  This fixes a buffer
     overflow for structure entries with more than 20 strings.
   
   PR:		kern/148546
   Reported by:	Spencer Minear @ McAfee
   MFC after:	3 days
 
 Modified:
   head/sys/dev/ipmi/ipmi_smbios.c
 
 Modified: head/sys/dev/ipmi/ipmi_smbios.c
 ==============================================================================
 --- head/sys/dev/ipmi/ipmi_smbios.c	Wed Jul 14 17:46:44 2010	(r210065)
 +++ head/sys/dev/ipmi/ipmi_smbios.c	Wed Jul 14 18:06:21 2010	(r210066)
 @@ -52,7 +52,7 @@ __FBSDID("$FreeBSD$");
  #define	pmap_unmapbios		pmap_unmapdev
  #endif
  
 -struct smbios_table_entry {
 +struct smbios_table {
  	uint8_t		anchor_string[4];
  	uint8_t		checksum;
  	uint8_t		length;
 @@ -108,27 +108,30 @@ struct ipmi_entry {
  #define	SMBIOS_LEN		4
  #define	SMBIOS_SIG		"_SM_"
  
 -typedef void (*dispatchproc_t)(uint8_t *p, char **table,
 -    struct ipmi_get_info *info);
 +typedef void (*smbios_callback_t)(struct structure_header *, void *);
  
  static struct ipmi_get_info ipmi_info;
  static int ipmi_probed;
  static struct mtx ipmi_info_mtx;
  MTX_SYSINIT(ipmi_info, &ipmi_info_mtx, "ipmi info", MTX_DEF);
  
 -static char	*get_strings(char *, char **);
  static void	ipmi_smbios_probe(struct ipmi_get_info *);
 -static int	smbios_cksum	(struct smbios_table_entry *);
 -static void	smbios_run_table(uint8_t *, int, dispatchproc_t *,
 -		    struct ipmi_get_info *);
 -static void	smbios_t38_proc_info(uint8_t *, char **,
 -		    struct ipmi_get_info *);
 +static int	smbios_cksum(struct smbios_table *);
 +static void	smbios_walk_table(uint8_t *, int, smbios_callback_t,
 +		    void *);
 +static void	smbios_ipmi_info(struct structure_header *, void *);
  
  static void
 -smbios_t38_proc_info(uint8_t *p, char **table, struct ipmi_get_info *info)
 +smbios_ipmi_info(struct structure_header *h, void *arg)
  {
 -	struct ipmi_entry *s = (struct ipmi_entry *)p;
 +	struct ipmi_get_info *info;
 +	struct ipmi_entry *s;
  
 +	if (h->type != 38 || h->length <
 +	    offsetof(struct ipmi_entry, interrupt_number))
 +		return;
 +	s = (struct ipmi_entry *)h;
 +	info = arg;
  	bzero(info, sizeof(struct ipmi_get_info));
  	switch (s->interface_type) {
  	case KCS_MODE:
 @@ -172,44 +175,28 @@ smbios_t38_proc_info(uint8_t *p, char **
  	info->iface_type = s->interface_type;
  }
  
 -static char *
 -get_strings(char *p, char **table)
 -{
 -	/* Scan for strings, stoping at a single null byte */
 -	while (*p != 0) {
 -		*table++ = p;
 -		p += strlen(p) + 1;
 -	}
 -	*table = 0;
 -
 -	/* Skip past terminating null byte */
 -	return (p + 1);
 -}
 -
 -
  static void
 -smbios_run_table(uint8_t *p, int entries, dispatchproc_t *dispatchstatus,
 -    struct ipmi_get_info *info)
 +smbios_walk_table(uint8_t *p, int entries, smbios_callback_t cb, void *arg)
  {
  	struct structure_header *s;
 -	char *table[20];
 -	uint8_t *nextp;
  
 -	while(entries--) {
 -		s = (struct structure_header *) p;
 -		nextp = get_strings(p + s->length, table);
 +	while (entries--) {
 +		s = (struct structure_header *)p;
 +		cb(s, arg);
  
  		/*
 -		 * No strings still has a double-null at the end,
 -		 * skip over it
 +		 * Look for a double-nul after the end of the
 +		 * formatted area of this structure.
  		 */
 -		if (table[0] == 0)
 -			nextp++;
 +		p += s->length;
 +		while (p[0] != 0 && p[1] != 0)
 +			p++;
  
 -		if (dispatchstatus[*p]) {
 -			(dispatchstatus[*p])(p, table, info);
 -		}
 -		p = nextp;
 +		/*
 +		 * Skip over the double-nul to the start of the next
 +		 * structure.
 +		 */
 +		p += 2;
  	}
  }
  
 @@ -221,8 +208,7 @@ smbios_run_table(uint8_t *p, int entries
  static void
  ipmi_smbios_probe(struct ipmi_get_info *info)
  {
 -	dispatchproc_t dispatch_smbios_ipmi[256];
 -	struct smbios_table_entry *header;
 +	struct smbios_table *header;
  	void *table;
  	u_int32_t addr;
  
 @@ -239,9 +225,9 @@ ipmi_smbios_probe(struct ipmi_get_info *
  	 * length and then map it a second time with the actual length so
  	 * we can verify the checksum.
  	 */
 -	header = pmap_mapbios(addr, sizeof(struct smbios_table_entry));
 +	header = pmap_mapbios(addr, sizeof(struct smbios_table));
  	table = pmap_mapbios(addr, header->length);
 -	pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_table_entry));
 +	pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_table));
  	header = table;
  	if (smbios_cksum(header) != 0) {
  		pmap_unmapbios((vm_offset_t)header, header->length);
 @@ -251,9 +237,7 @@ ipmi_smbios_probe(struct ipmi_get_info *
  	/* Now map the actual table and walk it looking for an IPMI entry. */
  	table = pmap_mapbios(header->structure_table_address,
  	    header->structure_table_length);
 -	bzero((void *)dispatch_smbios_ipmi, sizeof(dispatch_smbios_ipmi));
 -	dispatch_smbios_ipmi[38] = (void *)smbios_t38_proc_info;
 -	smbios_run_table(table, header->number_structures, dispatch_smbios_ipmi,
 +	smbios_walk_table(table, header->number_structures, smbios_ipmi_info,
  	    info);
  
  	/* Unmap everything. */
 @@ -298,7 +282,7 @@ ipmi_smbios_identify(struct ipmi_get_inf
  }
  
  static int
 -smbios_cksum (struct smbios_table_entry *e)
 +smbios_cksum(struct smbios_table *e)
  {
  	u_int8_t *ptr;
  	u_int8_t cksum;
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
State-Changed-From-To: open->patched 
State-Changed-By: jhb 
State-Changed-When: Wed Jul 14 18:21:10 UTC 2010 
State-Changed-Why:  
Fix committed to HEAD. 


Responsible-Changed-From-To: freebsd-bugs->jhb 
Responsible-Changed-By: jhb 
Responsible-Changed-When: Wed Jul 14 18:21:10 UTC 2010 
Responsible-Changed-Why:  
Fix committed to HEAD. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=148546 

From: Dmitrij Tejblum <tejblum@tejblum.yandex.ru>
To: bug-followup@FreeBSD.org, jhb@FreeBSD.org
Cc:  
Subject: Re: kern/148546: [ipmi] Buffer overrun in the impi driver while
 processing smbios date
Date: Wed, 28 Jul 2010 22:22:13 +0400 (MSD)

 Hi.
 
 The committed patch is incorrect -- it completely breaks IPMI probe. This 
 is because it actually search for single NUL instead of double-NUL. It 
 could be fixed like this (I've tested it):
 
 --- dev/ipmi/ipmi_smbios.c	2010-07-28 15:53:53.000000000 +0400
 +++ dev/ipmi/ipmi_smbios.c	2010-07-28 22:05:54.000000000 +0400
 @@ -187,11 +187,11 @@
   		/*
   		 * Look for a double-nul after the end of the
   		 * formatted area of this structure.
   		 */
   		p += s->length;
 -		while (p[0] != 0 && p[1] != 0)
 +		while (p[0] != 0 || p[1] != 0)
   			p++;
 
   		/*
   		 * Skip over the double-nul to the start of the next
   		 * structure.
 
 
 Please fix it ASAP, since the breakage is already in production branches, 
 and IPMI probe is critical on some servers.
 
 Thanks,
 
 Dima
 

From: Dmitrij Tejblum <tejblum@yandex-team.ru>
To: Cc: bug-followup@FreeBSD.org, jhb@FreeBSD.org
Subject: Re: kern/148546: [ipmi] Buffer overrun in the impi driver while
 processing smbios date
Date: Wed, 28 Jul 2010 22:33:07 +0400 (MSD)

 Oops, sorry for the noise, the "From" address in my previous email is 
 incorrect. Please use this one if you need to cobtact me.
 
 On Wed, 28 Jul 2010, Dmitrij Tejblum wrote:
 
 > The committed patch is incorrect -- it completely breaks IPMI probe. This is 
 > because it actually search for single NUL instead of double-NUL. It could be 
 > fixed like this (I've tested it):
 >
 > ....

From: John Baldwin <jhb@freebsd.org>
To: Dmitrij Tejblum <tejblum@yandex-team.ru>
Cc: bug-followup@freebsd.org
Subject: Re: kern/148546: [ipmi] Buffer overrun in the impi driver while processing smbios date
Date: Thu, 29 Jul 2010 09:13:31 -0400

 On Wednesday, July 28, 2010 2:22:13 pm Dmitrij Tejblum wrote:
 > Hi.
 > 
 > The committed patch is incorrect -- it completely breaks IPMI probe. This 
 > is because it actually search for single NUL instead of double-NUL. It 
 > could be fixed like this (I've tested it):
 > 
 > --- dev/ipmi/ipmi_smbios.c	2010-07-28 15:53:53.000000000 +0400
 > +++ dev/ipmi/ipmi_smbios.c	2010-07-28 22:05:54.000000000 +0400
 > @@ -187,11 +187,11 @@
 >   		/*
 >   		 * Look for a double-nul after the end of the
 >   		 * formatted area of this structure.
 >   		 */
 >   		p += s->length;
 > -		while (p[0] != 0 && p[1] != 0)
 > +		while (p[0] != 0 || p[1] != 0)
 >   			p++;
 > 
 >   		/*
 >   		 * Skip over the double-nul to the start of the next
 >   		 * structure.
 > 
 > 
 > Please fix it ASAP, since the breakage is already in production branches, 
 > and IPMI probe is critical on some servers.
 
 Odd, I tested the original patch on a machine that used SMBIOS to enumerate 
 the BMC.  I guess it would work ok for table entries that don't have any 
 following strings since the loop stops at the first nul, but the first nul 
 would be the double-nul for structures without any strings.
 
 -- 
 John Baldwin
State-Changed-From-To: patched->closed 
State-Changed-By: jhb 
State-Changed-When: Mon Aug 9 20:09:14 UTC 2010 
State-Changed-Why:  
Fixed all the way back to 6.x. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=148546 
>Unformatted:
