ibm_newemac and SIOCGMIIREG
RFC PATCH 1/1 ibm_newemac and SIOCGMIIREG
SIOCGMIIREG and SIOCSMIIREG access a user data structure via a void pointer to user space. So, we need copy_from_user and copy_to_user to move the data.
---
I believe there is a bug in the way the ibm_newemac driver handles the SIOCGMIIREG (and SIOCSMIIREG) ioctl. The problem is that emac_ioctl is handed a "struct ifreq *rq" which contains a user-land pointer to an array of 16-bit integers.
However, emac_ioctl directly accesses the data, which doesn't work. I added the following patch to copy the data in and out.
Please note that this patch was tested in an older kernel (2.6.30) because that is what we are using on our custom hardware. I think this is still a problem in the current code, but I'd like reviewers to take a look, to be sure.
home/sfalco/core.c renamed from net/ibm_newemac/core.c +11 -4
@@ -2218,6 +2218,7 @@ {struct emac_instance *dev =netdev_priv (ndev);struct mii_ioctl_data *data =if_mii (rq); +struct mii_ioctl_data user_data;DBG (dev,"ioctl %08x" NL , cmd);
@@ -2229,13 +2230,19 @@ data->phy_id = dev->phy.address;/* Fall through */ case SIOCGMIIREG : - data->val_out =emac_mdio_read (ndev, dev->phy.address, - data->reg_num); +if (copy_from_user (user_data, (char __user *)data,sizeof (user_data))) +return -EFAULT ; + user_data->val_out =emac_mdio_read (ndev, dev->phy.address, + user_data->reg_num); +if (copy_to_user ((char __user *)rq->ifr_data, user_data,sizeof (user_data))) +return -EFAULT ;return 0 ;case SIOCSMIIREG : -emac_mdio_write (ndev, dev->phy.address, data->reg_num, - data->val_in); +if (copy_from_user (user_data, (char __user *)data,sizeof (user_data))) +return -EFAULT ; +emac_mdio_write (ndev, dev->phy.address, user_data->reg_num, + user_data->val_in);return 0 ;default :return -EOPNOTSUPP ;
Quoted 4 lines · Steven A. Falco
I believe there is a bug in the way the
ibm_newemacdriver handles the SIOCGMIIREG (and SIOCSMIIREG) ioctl. The problem is thatemac_ioctlis handed a "struct ifreq *rq" which contains a user-land pointer to an array of 16-bit integers.
Did you actually see a bug here, or just think that this could be a problem?
Quoted 7 lines
However,
emac_ioctldirectly accesses the data, which doesn't work. I added the following patch to copy the data in and out.Please note that this patch was tested in an older kernel (2.6.30) because that is what we are using on our custom hardware. I think this is still a problem in the current code, but I'd like reviewers to take a look, to be sure.
The ifreq structure passed into the ndo_ioctl function is in kernel space, it gets copied there by net/core/dev.c:dev_ioctl(). emac_ioctl only accesses the data in that structure, so a copy_from_user is wrong here as far as I can tell.
Arnd
Quoted 9 lines · Arnd Bergmann
Quoted 4 lines · Steven A. Falco
I believe there is a bug in the way the
ibm_newemacdriver handles the SIOCGMIIREG (and SIOCSMIIREG) ioctl. The problem is thatemac_ioctlis handed a "struct ifreq *rq" which contains a user-land pointer to an array of 16-bit integers.Did you actually see a bug here, or just think that this could be a problem?
I did see a problem. I tried using the ioctl, and I didn't get the correct result. I then added some printk in the driver, and saw that garbage was being passed in the data array.
I added the copy_from/copy_to, and the ioctl started working.
Quoted 9 lines
Quoted 8 lines
However,
emac_ioctldirectly accesses the data, which doesn't work. I added the following patch to copy the data in and out.Please note that this patch was tested in an older kernel (2.6.30) because that is what we are using on our custom hardware. I think this is still a problem in the current code, but I'd like reviewers to take a look, to be sure.
Quoted 4 lines
The ifreq structure passed into the
ndo_ioctlfunction is in kernel space, it gets copied there by net/core/dev.c:dev_ioctl().emac_ioctlonly accesses the data in that structure, so acopy_from_useris wrong here as far as I can tell.
net/core/dev.c:dev_ioctl() does a copy_from_user on the overall structure, but the structure contains a union, one member of which is an embedded pointer to an array. This pointer member is only used in the case of these two ioctls. Other ioctls use different union members, which are not pointers.
As I understand it, the copy_from_user in dev_ioctl does not recursively copy the array. In fact it could not do so, because the pointer to array is only 4 bytes long, while the array itself is 8 bytes long - so it would not fit. I.e., dev_ioctl would have to allocate storage, and do a second copy_from_user to retrieve the array. It would have to clean up after the emac_ioctl ran. And it would have to do this only for these specific ioctl calls which use the array pointer in the union.
Also, the result has to be returned to the user in the same array, which needs a copy_to_user of the array data, which is also not done in dev_ioctl.
So I think this second copy_from/copy_to needs to be done somewhere. I added it in the emac_ioctl because that is where the command is fully decoded. It also avoids the problem of allocating space for the copied array. But other fixes are certainly possible as well, which is why I am not sure I've hit on the "proper" fix.
Thanks very much for reviewing - please let me know if my explanation is unclear, or if you see a better way to fix this problem.
Steve
Quoted 2 lines
Arnd
Signature · 4 lines
A: Because it makes the logic of the discussion difficult to follow. Q: Why shouldn't I top post? A: No. Q: Should I top post?
Quoted 9 lines · Steven A. Falco
Quoted 4 lines
The ifreq structure passed into the
ndo_ioctlfunction is in kernel space, it gets copied there by net/core/dev.c:dev_ioctl().emac_ioctlonly accesses the data in that structure, so acopy_from_useris wrong here as far as I can tell.net/core/dev.c:dev_ioctl() does a
copy_from_useron the overall structure, but the structure contains a union, one member of which is an embedded pointer to an array. This pointer member is only used in the case of these two ioctls. Other ioctls use different union members, which are not pointers.
Still unconvinced.
I don't see anywhere in the structure where we actually use a pointer from ifr_ifru. The if_mii function is defined as
static inline struct mii_ioctl_data *if_mii(struct ifreq *rq)
{
return (struct mii_ioctl_data *) &rq->ifr_ifru;
}That just returns a pointer to the ifr_ifru member itself, it does not read a __user pointer. Note how if_mii even returns a kernel pointer (struct mii_ioctl_data *), not a struct mii_ioctl_data __user *. You even added a cast that otherwise should not be needed.
Quoted 10 lines
As I understand it, the
copy_from_userindev_ioctldoes not recursively copy the array. In fact it could not do so, because the pointer to array is only 4 bytes long, while the array itself is 8 bytes long - so it would not fit. I.e.,dev_ioctlwould have to allocate storage, and do a secondcopy_from_userto retrieve the array. It would have to clean up after theemac_ioctlran. And it would have to do this only for these specific ioctl calls which use the array pointer in the union.Also, the result has to be returned to the user in the same array, which needs a
copy_to_userof the array data, which is also not done indev_ioctl.
There is no array, and no pointer in struct ifreq
Quoted 5 lines
So I think this second copy_from/copy_to needs to be done somewhere. I added it in the
emac_ioctlbecause that is where the command is fully decoded. It also avoids the problem of allocating space for the copied array. But other fixes are certainly possible as well, which is why I am not sure I've hit on the "proper" fix.
No other device driver implementing SIOCGMIIREG does any of this, so I'm pretty sure it's not the proper fix.
Quoted 2 lines
Thanks very much for reviewing - please let me know if my explanation is unclear, or if you see a better way to fix this problem.
In theory, your patch should break the code. Doing a direct access in place of a copy_to/from_user is a security hole but should still work, while adding a copy_to/from_user on a kernel pointer should always result in an EFAULT.
Arnd
Quoted 7 lines · Arnd Bergmann
Quoted 5 lines · Steven A. Falco
Quoted 4 lines
The ifreq structure passed into the
ndo_ioctlfunction is in kernel space, it gets copied there by net/core/dev.c:dev_ioctl().emac_ioctlonly accesses the data in that structure, so acopy_from_useris wrong here as far as I can tell.
Quoted 6 lines
Quoted 4 lines
net/core/dev.c:dev_ioctl() does a
copy_from_useron the overall structure, but the structure contains a union, one member of which is an embedded pointer to an array. This pointer member is only used in the case of these two ioctls. Other ioctls use different union members, which are not pointers.Still unconvinced.
Ok - here is the user-space program I am using to test this. It simply uses the ioctl to peek and poke the phy. If I run it on an unmodified kernel, and I read the phy ID registers, i.e. registers 2 and 3, I get:
# ./phy -r -a 2 2: 0000 # ./phy -r -a 3 3: 0000
which is clearly wrong. Once I load my modified kernel, I get:
# ./phy -a 2 2: 0022 # ./phy -a 3 3: 1512
which is correct for the phy on my board (i.e. phy id is 00221512). If you could try this program on a sequoia or similar, I'd be interested in whether you see the problem.
------ cut here ------
#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <linux/mii.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define READING 0
#define WRITING 1
void use()
{
fprintf(stderr, "\nphy [OPTIONS]\n\n");
fprintf(stderr, " -a address (register within phy to access)\n");
fprintf(stderr, " -d device (something like eth0, eth1, etc.)\n");
fprintf(stderr, " -r (read the register)\n");
fprintf(stderr, " -w (write the register)\n");
fprintf(stderr, " -v value (the value to write)\n");
fprintf(stderr, " -h (this help message)\n");
}intmain(int argc, char *argv[])
{
int fd;
struct ifreq request;
struct mii_ioctl_data data;
int opt;
int dir = READING;
char *pDev = "eth0";
uint32_t addr = 0;
uint32_t value = 0;
while((opt = getopt(argc, argv, "a:d:hrv:w")) != -1) {
switch(opt) {
case 'a':
addr = strtoul(optarg, NULL, 16);
break;
case 'd':
pDev = optarg;
break;
case 'h':
use();
exit(0);
case 'r':
dir = READING;
break;
case 'v':
value = strtoul(optarg, NULL, 16);
break;
case 'w':
dir = WRITING;
break;
default:
fprintf(stderr, "bad option\n");
use();
exit(1);
}
}
fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0) {
fprintf(stderr, "no sock\n");
exit(1);
}
memset(&request, 0, sizeof(request));
memset(&data, 0, sizeof(data));
strncpy(request.ifr_name, pDev, sizeof(request.ifr_name));
data.reg_num = addr;
y
if(dir == READING) {
if(ioctl(fd, SIOCGMIIREG, &request) < 0) {
fprintf(stderr, "SIOCGMIIREG failed\n");
exit(1);
}
printf("%d: %04x\n", addr, data.val_out);
} else {
data.val_in = value;
if(ioctl(fd, SIOCSMIIREG, &request) < 0) {
fprintf(stderr, "SIOCSMIIREG failed\n");
exit(1);
}
}
exit(0);
}------ cut here ------
Quoted 10 lines
I don't see anywhere in the structure where we actually use a pointer from
ifr_ifru. Theif_miifunction is defined asstatic inline struct
mii_ioctl_data*if_mii(struct ifreq *rq){ return (struct mii_ioctl_data *) &rq->ifr_ifru; }That just returns a pointer to the
ifr_ifrumember itself,
But that pointer points to a separate "struct mii_ioctl_data" in user space. In my test code above, I do:
request.ifr_data = (__caddr_t)&data;
where data is a "struct mii_ioctl_data" that is separate from the "struct ifreq". It's not one monolithic structure, it is two structures, one pointing to the other, both in user space.
Quoted 4 lines
it does not read a
__userpointer. Note howif_miieven returns a kernel pointer (structmii_ioctl_data*), not a structmii_ioctl_data__user*. You even added a cast that otherwise should not be needed.
I disagree - the structure, as shown in "include/linux/if.h" has "void __user *ifru_data", which is clearly a user pointer.
Quoted 9 lines
Quoted 9 lines
As I understand it, the
copy_from_userindev_ioctldoes not recursively copy the array. In fact it could not do so, because the pointer to array is only 4 bytes long, while the array itself is 8 bytes long - so it would not fit. I.e.,dev_ioctlwould have to allocate storage, and do a secondcopy_from_userto retrieve the array. It would have to clean up after theemac_ioctlran. And it would have to do this only for these specific ioctl calls which use the array pointer in the union.
Quoted 4 lines
Quoted 2 lines
Also, the result has to be returned to the user in the same array, which needs a
copy_to_userof the array data, which is also not done indev_ioctl.There is no array, and no pointer in struct ifreq
struct ifreq {
.....
union {
.....
void __user * ifru_data;
.....
} ifr_ifru;
};I claim that "ifru_data" is a pointer to a user-space structure, which for these two ioctls is defined as:
struct mii_ioctl_data {
__u16 phy_id;
__u16 reg_num;
__u16 val_in;
__u16 val_out;
};I misspoke when calling this an array. In earlier versions of the kernel code, this was simply an array of four __u16, but it is now a struct. Doesn't really matter though - it is a separate structure from the ifreq one. So in my test program, I have:
struct ifreq request;
struct mii_ioctl_data data;
.....
request.ifr_data = (__caddr_t)&data;which is where I fill in the "struct ifreq" with my user pointer to my "struct mii_ioctl_data".
The key is that the union does not have:
struct mii_ioctl_data ifru_data;
rather it has a pointer:
void __user * ifru_data;
and that is why the second copy_from_user is needed. It would have been much nicer if the union contained the actual mii_ioctl_data structure, but I guess the void pointer is more flexible.
Quoted 9 lines
Quoted 6 lines
So I think this second copy_from/copy_to needs to be done somewhere. I added it in the
emac_ioctlbecause that is where the command is fully decoded. It also avoids the problem of allocating space for the copied array. But other fixes are certainly possible as well, which is why I am not sure I've hit on the "proper" fix.No other device driver implementing SIOCGMIIREG does any of this, so I'm pretty sure it's not the proper fix.
That is my main worry as well. However, my test program does not work unless I modify the kernel. Perhaps there is something wrong with the program, but I don't think so, based on the union definition as I showed it above.
Quoted 8 lines
Quoted 3 lines
Thanks very much for reviewing - please let me know if my explanation is unclear, or if you see a better way to fix this problem.
In theory, your patch should break the code. Doing a direct access in place of a copy_to/from_user is a security hole but should still work, while adding a copy_to/from_user on a kernel pointer should always result in an EFAULT.
My patch does not give an EFAULT, as you will see if you are able to try my test program. I won't claim that proves I am right however. :-)
Your statement that copy_to/from_user is just there for security is something I did not know. Yet, that doesn't appear to be the case, given my experiments.
I appreciate your patience - perhaps there is something unique about the 4xx CPU's that makes the direct access fail. I should add that my kernel does have Xenomai patches added. I also don't know if that has any bearing on the problem.
I'll try to resurrect my sequoia board with an unmodified kernel and test further. One thing for sure - something strange is going on.
Steve
Quoted 3 lines
Arnd
Quoted 14 lines · On Thursday 10 June 2010 21:47:27 Steven A. Falco wrote:
Quoted 1 line · Arnd Bergmann
Still unconvinced.
Ok - here is the user-space program I am using to test this. It simply uses the ioctl to peek and poke the phy. If I run it on an unmodified kernel, and I read the phy ID registers, i.e. registers 2 and 3, I get:
# ./phy -r -a 2 2: 0000 # ./phy -r -a 3 3: 0000which is clearly wrong. Once I load my modified kernel, I get:
Quoted 8 lines
# ./phy -a 2 2: 0022 # ./phy -a 3 3: 1512which is correct for the phy on my board (i.e. phy id is 00221512). If you could try this program on a sequoia or similar, I'd be interested in whether you see the problem.
It seems that your program is wrong. On my PC here, it also shows zero, while mii-tool works fine.
Quoted 6 lines
int
main(int argc, char *argv[]){ int fd; struct ifreq request; struct mii_ioctl_data data;
This should be
struct mii_ioctl_data *data = &request.ifr_ifru
Quoted 10 lines
Quoted 9 lines
I don't see anywhere in the structure where we actually use a pointer from
ifr_ifru. Theif_miifunction is defined asstatic inline struct
mii_ioctl_data*if_mii(struct ifreq *rq){ return (struct mii_ioctl_data *) &rq->ifr_ifru; }That just returns a pointer to the
ifr_ifrumember itself,
Quoted 8 lines
But that pointer points to a separate "struct
mii_ioctl_data" in user space. In my test code above, I do:request.ifr_data = (__caddr_t)&data;
where data is a "struct
mii_ioctl_data" that is separate from the "struct ifreq". It's not one monolithic structure, it is two structures, one pointing to the other, both in user space.
Only in your code, when you look at mii-tool, it does (correctly)
static int mdio_read(int skfd, int location)
{
struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
mii->reg_num = location;
if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) {
fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,
strerror(errno));
return -1;
}
return mii->val_out;
}Quoted 7 lines
Quoted 4 lines
it does not read a
__userpointer. Note howif_miieven returns a kernel pointer (structmii_ioctl_data*), not a structmii_ioctl_data__user*. You even added a cast that otherwise should not be needed.I disagree - the structure, as shown in "
include/linux/if.h" has "void__user*ifru_data", which is clearly a user pointer.
This is used for SIOCSHWTSTAMP, SIOCBONDINFOQUERY, SIOCETHTOOL and possibly a few others but not SIOCGMIIREG.
Quoted 3 lines
and that is why the second
copy_from_useris needed. It would have been much nicer if the union contained the actualmii_ioctl_datastructure, but I guess the void pointer is more flexible.
No, the void pointer is broken for a number of reasons, that's why we don't use it.
Quoted 6 lines
My patch does not give an EFAULT, as you will see if you are able to try my test program. I won't claim that proves I am right however. :-)
Your statement that copy_to/from_user is just there for security is something I did not know. Yet, that doesn't appear to be the case, given my experiments.
Right, I misread one line of your patch: when you do
if (copy_from_user(user_data, (char __user *)data, sizeof(user_data))) return -EFAULT; user_data->val_out = emac_mdio_read(ndev, dev->phy.address, user_data->reg_num); if (copy_to_user((char __user *)rq->ifr_data, user_data, sizeof(user_data)))
You actually copy in from a different pointer than you copy out to. The first one is a cast from a kernel to a user pointer, which should actually result in -EFAULT (I don't known why it doesn't) while the second one is where you change the interface to match your (broken) program.
Arnd
Quoted 4 lines · Arnd Bergmann
It seems that your program is wrong. On my PC here, it also shows zero, while mii-tool works fine.
You are correct. I didn't realize I should embed the data in the ifr_data area - I thought I needed to pass a pointer.
Now I see why you avoid that. And since IFNAMSIZ is 16, there is enough space for the struct mii_ioctl_data. A bit dirty, but it obviously works.
Anyway, thanks very much for taking the time to debug my program, and set me straight. Naturally, I withdraw the patch. :-)
Steve
Quoted 2 lines
Arnd