Discussion:
[2.6 patch] fs/smbfs/request.c: fix NULL dereference
(too old to reply)
Adrian Bunk
2005-03-25 02:08:40 UTC
Permalink
The Coverity checker found that if req was NULL because find_request
returned NULL, this resulted in a break from the switch, but req was
later dereferenced (look at the last line of this patch).

Signed-off-by: Adrian Bunk <***@stusta.de>

--- linux-2.6.12-rc1-mm2-full/fs/smbfs/request.c.old 2005-03-25 00:45:08.000000000 +0100
+++ linux-2.6.12-rc1-mm2-full/fs/smbfs/request.c 2005-03-25 00:47:14.000000000 +0100
@@ -783,20 +783,23 @@ int smb_request_recv(struct smb_sb_info
break;
break;

/* We should never be called with any of these states */
case SMB_RECV_END:
case SMB_RECV_REQUEST:
server->rstate = SMB_RECV_END;
break;
}

+ if (!req)
+ return -ENOMEM;
+
if (result < 0) {
/* We saw an error */
return result;
}

if (server->rstate != SMB_RECV_END)
return 0;

result = 0;
if (req->rq_trans2_command && req->rq_rcls == SUCCESS)

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



-------------------------------------------------------------------------------
Achtung: diese Newsgruppe ist eine unidirektional gegatete Mailingliste.
Antworten nur per Mail an die im Reply-To-Header angegebene Adresse.
Fragen zum Gateway -> ***@inka.de.
-------------------------------------------------------------------------------
Jean Delvare
2005-03-26 09:05:54 UTC
Permalink
Hi all,
Post by Adrian Bunk
The Coverity checker found that if req was NULL because find_request
returned NULL, this resulted in a break from the switch, but req was
later dereferenced (look at the last line of this patch).
--- linux-2.6.12-rc1-mm2-full/fs/smbfs/request.c.old 2005-03-25 00:45:08.000000000 +0100
+++ linux-2.6.12-rc1-mm2-full/fs/smbfs/request.c 2005-03-25 00:47:14.000000000 +0100
@@ -783,20 +783,23 @@ int smb_request_recv(struct smb_sb_info
break;
break;
/* We should never be called with any of these states */
server->rstate = SMB_RECV_END;
break;
}
+ if (!req)
+ return -ENOMEM;
+
if (result < 0) {
/* We saw an error */
return result;
}
if (server->rstate != SMB_RECV_END)
return 0;
result = 0;
if (req->rq_trans2_command && req->rq_rcls == SUCCESS)
Patch is broken, see:
http://bugzilla.kernel.org/show_bug.cgi?id=4403

Andrew, please back out from -mm.

The smb_request_recv function is a complex one. The NULL dereference
spotted by Coverity exists, but the fix proposed here doesn't properly
address the problem.

The big switch with fallthroughs and breaks all around the place is
really tricky and hard to understand, admittedly. Not sure it is the
best approach, BTW, but that's beyond the point.

The problem with the patch proposed by Adrian is that it sometimes
breaks (ah ah) in the SMB_RECV_DROP, SMB_RECV_START and SMB_RECV_HEADER
cases, because req was initialized to NULL in the first place, and is
not touched in these cases. That's not a problem if we fall through the
switch cases down to at least SMB_RECV_COMPLETE, which will attempt to
allocate req, but we might as well exit in one the five early breaks in
SMB_RECV_DROP and SMB_RECV_HEADER. If we do, we'll get caught by the
check Adrian added, and -ENOMEM will be returned, while no memory
allocation was attempted!

The same is true for the SMB_RECV_END and SMB_REQUEST, but according to
the comment these shouldn't happen anyway. As a side note, I wonder why
there isn't even a warning thrown in the logs here, as we certainly
would love to know when something happens that wasn't supposed to.

My own proposed replacement patch follows. What to do in the
SMB_RECV_END and SMB_REQUEST (and default?) cases depends on what the
comment really means. If it means that the case should never happen
unless there's a bug elsewhere in the kernel, then what I did is
probably correct. But if the state is received from the outside and we
have no control on it, then maybe attempting to still process the
request the best we can might make sense. I don't know, people who do,
please speak out.

Patch testing in progress here, so far so good, and no warning message
in the logs either.

Thanks.

--- linux-2.6.12-rc1-mm3/fs/smbfs/request.c.orig 2005-03-26 08:39:48.000000000 +0100
+++ linux-2.6.12-rc1-mm3/fs/smbfs/request.c 2005-03-26 09:48:24.000000000 +0100
@@ -754,8 +754,10 @@
/* fallthrough */
case SMB_RECV_HCOMPLETE:
req = find_request(server, WVAL(server->header, smb_mid));
- if (!req)
+ if (!req) {
+ result = -ENOMEM;
break;
+ }
smb_init_request(server, req);
req->rq_rcls = *(req->rq_header + smb_rcls);
req->rq_err = WVAL(req->rq_header, smb_err);
@@ -765,8 +767,10 @@
case SMB_RECV_PARAM:
if (!req)
req = find_request(server,WVAL(server->header,smb_mid));
- if (!req)
+ if (!req) {
+ result = -ENOMEM;
break;
+ }
result = smb_recv_param(server, req);
if (result < 0)
break;
@@ -776,8 +780,10 @@
case SMB_RECV_DATA:
if (!req)
req = find_request(server,WVAL(server->header,smb_mid));
- if (!req)
+ if (!req) {
+ result = -ENOMEM;
break;
+ }
result = smb_recv_data(server, req);
if (result < 0)
break;
@@ -786,8 +792,10 @@
/* We should never be called with any of these states */
case SMB_RECV_END:
case SMB_RECV_REQUEST:
- server->rstate = SMB_RECV_END;
- break;
+ default:
+ printk(KERN_WARNING "smbfs: unexpected server->rstate %d\n",
+ server->rstate);
+ result = -EINVAL;
}

if (result < 0) {
--
Jean Delvare
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



-------------------------------------------------------------------------------
Achtung: diese Newsgruppe ist eine unidirektional gegatete Mailingliste.
Antworten nur per Mail an die im Reply-To-Header angegebene Adresse.
Fragen zum Gateway -> ***@inka.de.
-------------------------------------------------------------------------------
Adrian Bunk
2005-03-26 12:55:36 UTC
Permalink
Post by Jean Delvare
Hi all,
Hi Jean,
Post by Jean Delvare
http://bugzilla.kernel.org/show_bug.cgi?id=4403
Andrew, please back out from -mm.
agreed, thanks for reporting this.
Post by Jean Delvare
The smb_request_recv function is a complex one. The NULL dereference
spotted by Coverity exists, but the fix proposed here doesn't properly
address the problem.
The big switch with fallthroughs and breaks all around the place is
really tricky and hard to understand, admittedly. Not sure it is the
best approach, BTW, but that's beyond the point.
...
The same is true for the SMB_RECV_END and SMB_REQUEST, but according to
the comment these shouldn't happen anyway. As a side note, I wonder why
there isn't even a warning thrown in the logs here, as we certainly
would love to know when something happens that wasn't supposed to.
My own proposed replacement patch follows. What to do in the
SMB_RECV_END and SMB_REQUEST (and default?) cases depends on what the
comment really means. If it means that the case should never happen
unless there's a bug elsewhere in the kernel, then what I did is
probably correct. But if the state is received from the outside and we
have no control on it, then maybe attempting to still process the
request the best we can might make sense. I don't know, people who do,
please speak out.
...
My impression is that your patch is incorrect, too.

The real problem seems to be:

<-- snip -->

...
/* We should never be called with any of these states */
case SMB_RECV_END:
case SMB_RECV_REQUEST:
server->rstate = SMB_RECV_END;
break;
...
if (server->rstate != SMB_RECV_END)
return 0;

result = 0;
if (req->rq_trans2_command && req->rq_rcls == SUCCESS)
result = smb_recv_trans2(server, req);
[more code]
...

<-- snip -->

A second try:

The problem is actually only in the SMB_RECV_END and SMB_RECV_REQUEST
cases and all code after the NULL pointer dereference is actually dead
code.

It turned out it removes much code, but unless I've made another mistake
in understanding the code in question, all it actually does is replacing
a NULL pointer dereference with a BUG().

The code after the NULL/BUG is also changed by this, but I'd expect that
in this "impossible" case the problems are bigger and there's no proper
handling anyway.
Post by Jean Delvare
Thanks.
...
cu
Adrian

<-- snip -->

In a case documented as

We should never be called with any of these states

, replace a NULL pointer dereference with a BUG() and remove all code
that would only have been called after the NULL pointer dereference.

Signed-off-by: Adrian Bunk <***@stusta.de>

---

fs/smbfs/request.c | 171 ---------------------------------------------
1 files changed, 1 insertion(+), 170 deletions(-)

--- linux-2.6.12-rc1-mm3-full/fs/smbfs/request.c.old 2005-03-26 13:19:19.000000000 +0100
+++ linux-2.6.12-rc1-mm3-full/fs/smbfs/request.c 2005-03-26 13:41:30.000000000 +0100
@@ -564,152 +564,8 @@ out:
return result;
}

/*
- * Receive a transaction2 response
- * Return: 0 if the response has been fully read
- * 1 if there are further "fragments" to read
- * <0 if there is an error
- */
-static int smb_recv_trans2(struct smb_sb_info *server, struct smb_request *req)
-{
- unsigned char *inbuf;
- unsigned int parm_disp, parm_offset, parm_count, parm_tot;
- unsigned int data_disp, data_offset, data_count, data_tot;
- int hdrlen = SMB_HEADER_LEN + req->rq_resp_wct*2 - 2;
-
- VERBOSE("handling trans2\n");
-
- inbuf = req->rq_header;
- data_tot = WVAL(inbuf, smb_tdrcnt);
- parm_tot = WVAL(inbuf, smb_tprcnt);
- parm_disp = WVAL(inbuf, smb_prdisp);
- parm_offset = WVAL(inbuf, smb_proff);
- parm_count = WVAL(inbuf, smb_prcnt);
- data_disp = WVAL(inbuf, smb_drdisp);
- data_offset = WVAL(inbuf, smb_droff);
- data_count = WVAL(inbuf, smb_drcnt);
-
- /* Modify offset for the split header/buffer we use */
- if (data_count || data_offset) {
- if (unlikely(data_offset < hdrlen))
- goto out_bad_data;
- else
- data_offset -= hdrlen;
- }
- if (parm_count || parm_offset) {
- if (unlikely(parm_offset < hdrlen))
- goto out_bad_parm;
- else
- parm_offset -= hdrlen;
- }
-
- if (parm_count == parm_tot && data_count == data_tot) {
- /*
- * This packet has all the trans2 data.
- *
- * We setup the request so that this will be the common
- * case. It may be a server error to not return a
- * response that fits.
- */
- VERBOSE("single trans2 response "
- "dcnt=%u, pcnt=%u, doff=%u, poff=%u\n",
- data_count, parm_count,
- data_offset, parm_offset);
- req->rq_ldata = data_count;
- req->rq_lparm = parm_count;
- req->rq_data = req->rq_buffer + data_offset;
- req->rq_parm = req->rq_buffer + parm_offset;
- if (unlikely(parm_offset + parm_count > req->rq_rlen))
- goto out_bad_parm;
- if (unlikely(data_offset + data_count > req->rq_rlen))
- goto out_bad_data;
- return 0;
- }
-
- VERBOSE("multi trans2 response "
- "frag=%d, dcnt=%u, pcnt=%u, doff=%u, poff=%u\n",
- req->rq_fragment,
- data_count, parm_count,
- data_offset, parm_offset);
-
- if (!req->rq_fragment) {
- int buf_len;
-
- /* We got the first trans2 fragment */
- req->rq_fragment = 1;
- req->rq_total_data = data_tot;
- req->rq_total_parm = parm_tot;
- req->rq_ldata = 0;
- req->rq_lparm = 0;
-
- buf_len = data_tot + parm_tot;
- if (buf_len > SMB_MAX_PACKET_SIZE)
- goto out_too_long;
-
- req->rq_trans2bufsize = buf_len;
- req->rq_trans2buffer = smb_kmalloc(buf_len, GFP_NOFS);
- if (!req->rq_trans2buffer)
- goto out_no_mem;
- memset(req->rq_trans2buffer, 0, buf_len);
-
- req->rq_parm = req->rq_trans2buffer;
- req->rq_data = req->rq_trans2buffer + parm_tot;
- } else if (unlikely(req->rq_total_data < data_tot ||
- req->rq_total_parm < parm_tot))
- goto out_data_grew;
-
- if (unlikely(parm_disp + parm_count > req->rq_total_parm ||
- parm_offset + parm_count > req->rq_rlen))
- goto out_bad_parm;
- if (unlikely(data_disp + data_count > req->rq_total_data ||
- data_offset + data_count > req->rq_rlen))
- goto out_bad_data;
-
- inbuf = req->rq_buffer;
- memcpy(req->rq_parm + parm_disp, inbuf + parm_offset, parm_count);
- memcpy(req->rq_data + data_disp, inbuf + data_offset, data_count);
-
- req->rq_ldata += data_count;
- req->rq_lparm += parm_count;
-
- /*
- * Check whether we've received all of the data. Note that
- * we use the packet totals -- total lengths might shrink!
- */
- if (req->rq_ldata >= data_tot && req->rq_lparm >= parm_tot) {
- req->rq_ldata = data_tot;
- req->rq_lparm = parm_tot;
- return 0;
- }
- return 1;
-
-out_too_long:
- printk(KERN_ERR "smb_trans2: data/param too long, data=%u, parm=%u\n",
- data_tot, parm_tot);
- goto out_EIO;
-out_no_mem:
- printk(KERN_ERR "smb_trans2: couldn't allocate data area of %d bytes\n",
- req->rq_trans2bufsize);
- req->rq_errno = -ENOMEM;
- goto out;
-out_data_grew:
- printk(KERN_ERR "smb_trans2: data/params grew!\n");
- goto out_EIO;
-out_bad_parm:
- printk(KERN_ERR "smb_trans2: invalid parms, disp=%u, cnt=%u, tot=%u, ofs=%u\n",
- parm_disp, parm_count, parm_tot, parm_offset);
- goto out_EIO;
-out_bad_data:
- printk(KERN_ERR "smb_trans2: invalid data, disp=%u, cnt=%u, tot=%u, ofs=%u\n",
- data_disp, data_count, data_tot, data_offset);
-out_EIO:
- req->rq_errno = -EIO;
-out:
- return req->rq_errno;
-}
-
-/*
* State machine for receiving responses. We handle the fact that we can't
* read the full response in one try by having states telling us how much we
* have read.
*
@@ -785,39 +641,14 @@ int smb_request_recv(struct smb_sb_info

/* We should never be called with any of these states */
case SMB_RECV_END:
case SMB_RECV_REQUEST:
- server->rstate = SMB_RECV_END;
- break;
+ BUG();
}

if (result < 0) {
/* We saw an error */
return result;
}

- if (server->rstate != SMB_RECV_END)
- return 0;
-
- result = 0;
- if (req->rq_trans2_command && req->rq_rcls == SUCCESS)
- result = smb_recv_trans2(server, req);
-
- /*
- * Response completely read. Drop any extra bytes sent by the server.
- * (Yes, servers sometimes add extra bytes to responses)
- */
- VERBOSE("smb_len: %d smb_read: %d\n",
- server->smb_len, server->smb_read);
- if (server->smb_read < server->smb_len)
- smb_receive_drop(server);
-
- server->rstate = SMB_RECV_START;
-
- if (!result) {
- list_del_init(&req->rq_queue);
- req->rq_flags |= SMB_REQ_RECEIVED;
- smb_rput(req);
- wake_up_interruptible(&req->rq_wait);
- }
return 0;
}

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



-------------------------------------------------------------------------------
Achtung: diese Newsgruppe ist eine unidirektional gegatete Mailingliste.
Antworten nur per Mail an die im Reply-To-Header angegebene Adresse.
Fragen zum Gateway -> ***@inka.de.
-------------------------------------------------------------------------------
Adrian Bunk
2005-03-26 13:13:12 UTC
Permalink
Post by Adrian Bunk
...
The problem is actually only in the SMB_RECV_END and SMB_RECV_REQUEST
cases and all code after the NULL pointer dereference is actually dead
code.
...
OK, this was also wrong...

Third try.

cu
Adrian

<-- snip -->

In a case documented as

We should never be called with any of these states

BUG() in a case that would later result in a NULL pointer dereference.

Signed-off-by: Adrian Bunk <***@stusta.de>

--- linux-2.6.12-rc1-mm3-full/fs/smbfs/request.c.old 2005-03-26 13:19:19.000000000 +0100
+++ linux-2.6.12-rc1-mm3-full/fs/smbfs/request.c 2005-03-26 13:41:30.000000000 +0100
@@ -786,8 +642,7 @@ int smb_request_recv(struct smb_sb_info
/* We should never be called with any of these states */
case SMB_RECV_END:
case SMB_RECV_REQUEST:
- server->rstate = SMB_RECV_END;
- break;
+ BUG();
}

if (result < 0) {
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



-------------------------------------------------------------------------------
Achtung: diese Newsgruppe ist eine unidirektional gegatete Mailingliste.
Antworten nur per Mail an die im Reply-To-Header angegebene Adresse.
Fragen zum Gateway -> ***@inka.de.
-------------------------------------------------------------------------------
Jean Delvare
2005-03-26 18:18:20 UTC
Permalink
Hi Adrian,
Post by Adrian Bunk
Post by Adrian Bunk
...
The problem is actually only in the SMB_RECV_END and
SMB_RECV_REQUEST cases and all code after the NULL pointer
dereference is actually dead code.
...
OK, this was also wrong...
I can confirm, I gave it a try and had to reboot ;)

You are right that the problem is only in the SMB_RECV_END and
SMB_RECV_REQUEST cases. I had missed that point in the patch I proposed.
Post by Adrian Bunk
Third try.
(...)
In a case documented as
We should never be called with any of these states
BUG() in a case that would later result in a NULL pointer dereference.
(...)
--- linux-2.6.12-rc1-mm3-full/fs/smbfs/request.c.old 2005-03-26 13:19:19.000000000 +0100
+++ linux-2.6.12-rc1-mm3-full/fs/smbfs/request.c 2005-03-26 13:41:30.000000000 +0100
@@ -786,8 +642,7 @@ int smb_request_recv(struct smb_sb_info
/* We should never be called with any of these states */
- server->rstate = SMB_RECV_END;
- break;
+ BUG();
}
Yes, after reading the whole thing again, it seems to be the correct
thing to do, providing that "should never" is a reference to an internal
state and not something from the outside. I don't know myself, but you
seem to do. Maybe someone from the samba team could confirm?

BTW, it looks to me like Urban Widmark, the author of this module and
supposedly the maintainer of it as well, has vanished some times ago.
Last seen 2004-06-21, and no working e-mail address (both failes for
me). Shouldn't we mark smbfs as unmaintained in MAINTAINERS, or have
someone else take over? Any volunteer?

Thanks,
--
Jean Delvare
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



-------------------------------------------------------------------------------
Achtung: diese Newsgruppe ist eine unidirektional gegatete Mailingliste.
Antworten nur per Mail an die im Reply-To-Header angegebene Adresse.
Fragen zum Gateway -> ***@inka.de.
-------------------------------------------------------------------------------
Loading...