Skip to contentSkip to conversation

copy_from_user problem

DISCUSSION Maynard Johnson 5 participants 11 messages Feb 26, 2008 – Feb 27, 2008

lore
Share & tools
lore
#0 lore raw

Hi,
I'm developing a kernel module that needs to parse the in-memory ELF objects for a shared library (libc, to be specific). When running my test on a 32-bit library, it works fine, but for a 64-bit library, the very first copy_from_user() fails:
Elf64_Ehdr ehdr;
copy_from_user(&ehdr, location_of_lib, sizeof(Elf64_Ehdr);

I talked this over a bit with Will Schmidt. He determined that access_ok (being done as a result of copy_from_user) was failing, but we don't know why. I have 32-bit and 64-bit testcases that start up and then pause, waiting for input. We look at the entry for libc in /proc/<pid>/maps, and the permissions are the same for both 32-bit and 64-bit.

I've run this test on both a stock SLES 10 SP1 kernel and on 2.6.24. I'm sure this is a user error, but for the life of me, I don't know what I'm doing wrong.

Can anyone out there help?

Thanks.
Maynard Johnson
LTC POWER Toolchain

#1 lore raw
Quoted 15 lines · Maynard Johnson

Hi,
I'm developing a kernel module that needs to parse the in-memory ELF objects for a shared library (libc, to be specific). When running my test on a 32-bit library, it works fine, but for a 64-bit library, the very first copy_from_user() fails:

    Elf64_Ehdr ehdr;
    copy_from_user(&ehdr, location_of_lib, sizeof(Elf64_Ehdr);

I talked this over a bit with Will Schmidt. He determined that access_ok (being done as a result of copy_from_user) was failing, but we don't know why. I have 32-bit and 64-bit testcases that start up and then pause, waiting for input. We look at the entry for libc in /proc/<pid>/maps, and the permissions are the same for both 32-bit and 64-bit.

Quoted 5 lines

I've run this test on both a stock SLES 10 SP1 kernel and on 2.6.24. I'm sure this is a user error, but for the life of me, I don't know what I'm doing wrong.

Can anyone out there help?

I would have to look at the code.

Ben.

#2 lore raw
Quoted 6 lines · Benjamin Herrenschmidt
Quoted 5 lines · Maynard Johnson

Hi,
I'm developing a kernel module that needs to parse the in-memory ELF objects for a shared library (libc, to be specific). When running my test on a 32-bit library, it works fine, but for a 64-bit library, the very first copy_from_user() fails:

    Elf64_Ehdr ehdr;
    copy_from_user(&ehdr, location_of_lib, sizeof(Elf64_Ehdr);
Quoted 7 lines
Quoted 5 lines

I talked this over a bit with Will Schmidt. He determined that access_ok (being done as a result of copy_from_user) was failing, but we don't know why. I have 32-bit and 64-bit testcases that start up and then pause, waiting for input. We look at the entry for libc in /proc/<pid>/maps, and the permissions are the same for both 32-bit and 64-bit.

I've run this test on both a stock SLES 10 SP1 kernel and on 2.6.24. I'm sure this is a user error, but for the life of me, I don't know what I'm doing wrong.

Can anyone out there help?

I would have to look at the code.

Ben,
I've pared down the code to a minimal testcase and attached the source file. Here are the makefile rules to build it:

----------------------------------------------
obj-m := uaccess_test.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:

	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
	rm -f *.mod.c *.ko *.o .*.cmd
	rm -rf .tmp_versions

----------------------------------------------

Instructions:
1. Write a simple C program that will pause, waiting for input, so that you can obtain the address of libc to pass into the uaccess_test kernel module. For example:
#include <stdio.h>

int main(void)

{
        printf("Press Enter to continue.\n");
        getchar();

       return 0;
}
--------------
2. Compile C program as 32-bit; then run it.  While the program is waiting for input, obtain its PID and do 'cat /proc/<pid>/maps' to get the address of where libc is loaded.
3. From the dir where you build the uaccess_test kernel module:
        'insmod ./uaccess_test.ko lib_addr=0x<mem_loc_libc>'

This should succeed. dmesg to verify.
4. Unload the module.
5. Recompile your C program with -m64; start it up and obtain the address of libc again (now a 64-bit address).
6. Load the uaccess_test kernel module and pass 'lib_addr=0x<mem_loc_libc>'. Note that this time, the load fails. dmesg to see debug printk's.

Thanks for any light you can shed on this!

-Maynard

Quoted 4 lines

Ben.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/uaccess.h>

static long lib_addr;
module_param(lib_addr, long, 0);
MODULE_PARM_DESC(lib_addr, "lib_addr");

static unsigned long parse_elf64(unsigned long start_loc)

{
	Elf64_Ehdr * ehdr;
	int ret = 0;

	ehdr = kmalloc(sizeof(Elf64_Ehdr), GFP_KERNEL);
	if (copy_from_user((void *)ehdr, (void *) start_loc, sizeof(Elf64_Ehdr))) {
		printk("cannot get Elf64_Ehdr from "
		       "start_loc %lx\n", start_loc);
			goto out;
	}
	if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
		printk("EI_CLASS of Elf64_Hdr is incorrect! %d\n",
		       ehdr->e_ident[EI_CLASS]);
		goto out;
	}
	if (ehdr->e_type != ET_DYN) {
		printk(KERN_INFO "LPA: "
		       "%s, line %d: Unexpected e_type %u parsing ELF\n",
		       __FUNCTION__, __LINE__, ehdr->e_type);
		goto out;
	}
	ret = ehdr->e_ident[EI_CLASS];
	printk(KERN_INFO "Elf class from Ehdr is %d\n", ret);
 out:
	return ret;
}

static unsigned long parse_elf32(unsigned long start_loc)

{
	Elf32_Ehdr ehdr;
	int ret = 0;

	if (copy_from_user(&ehdr, (void *) start_loc, sizeof (ehdr)))
		goto out;
	if (ehdr.e_ident[EI_CLASS] != ELFCLASS32)
		goto out;
	if (ehdr.e_type != ET_DYN) {
		printk(KERN_INFO
		       "%s, line %d: Unexpected e_type %u parsing ELF\n",
		       __FUNCTION__, __LINE__, ehdr.e_type);
		goto out;
	}
	ret = ehdr.e_ident[EI_CLASS];
	printk(KERN_INFO "Elf class from Ehdr is %d\n", ret);
 out:
	return ret;

}

int find_ehdr(unsigned long start_loc)

{
	int ret = 0;
	if (!(ret = parse_elf32(start_loc)))
		ret = parse_elf64(start_loc);
	return ret;
}

int __init init_module(void)

{
	if (!(find_ehdr(lib_addr))) {
		printk(KERN_INFO "uaccess test failed\n");
		return -1;
	}
	    printk(KERN_INFO "uaccess test succeeded\n");
        return 0;
}

void __exit cleanup_module(void)
{
}
MODULE_LICENSE("GPL");

#3 lore raw
Quoted 3 lines · Maynard Johnson

static long lib_addr;
module_param(lib_addr, long, 0);

                         ^^^^

Should be unsigned long?

#4 lore raw
Quoted 6 lines · Nathan Lynch
Quoted 3 lines · Maynard Johnson

static long lib_addr;
module_param(lib_addr, long, 0);

                         ^^^^

Should be unsigned long?

ulong, rather, but that doesn't fix it.

In any case, lib_addr is a user virtual address; doesn't the kernel need to do get_user_pages or some such to get at arbitrary process memory?

#5 lore raw
In reply to Nathan Lynch
Quoted 5 lines · Nathan Lynch
Quoted 2 lines · Maynard Johnson

static long lib_addr;
module_param(lib_addr, long, 0);

                         ^^^^

Should be unsigned long?

Right. I switched this to 'ulong', but that didn't make a difference in my test results.

Quoted 1 line
#6 lore raw
In reply to Maynard Johnson
Quoted 5 lines · Maynard Johnson

2. Compile C program as 32-bit; then run it. While the program is waiting for input, obtain its PID and do 'cat /proc/<pid>/maps' to get
the address of where libc is loaded.
3. From the dir where you build the uaccess_test kernel module:

Quoted 8 lines

'insmod ./uaccess_test.ko lib_addr=0x<mem_loc_libc>'
This should succeed. dmesg to verify.
4. Unload the module.
5. Recompile your C program with -m64; start it up and obtain the address of libc again (now a 64-bit address).
6. Load the uaccess_test kernel module and pass 'lib_addr=0x<mem_loc_libc>'. Note that this time, the load fails. dmesg to see debug printk's.

Sounds to me that your kernel module will try to copy_from_user() from the user context of ... insmod :-)

You need to do your copy_from_user() from within the context of the program you try to access the memory from !

If you need to access another context than the current one, you then need to use a different mechanism, such as get_user_pages(), though beware that you can only do that for memory, not SPE local store or register mappings.

Ben.

#7 lore raw
Quoted 12 lines · Benjamin Herrenschmidt
Quoted 9 lines · Maynard Johnson

2. Compile C program as 32-bit; then run it. While the program is waiting for input, obtain its PID and do 'cat /proc/<pid>/maps' to get the address of where libc is loaded.
3. From the dir where you build the uaccess_test kernel module:
'insmod ./uaccess_test.ko lib_addr=0x<mem_loc_libc>'
This should succeed. dmesg to verify.
4. Unload the module.
5. Recompile your C program with -m64; start it up and obtain the address of libc again (now a 64-bit address).
6. Load the uaccess_test kernel module and pass 'lib_addr=0x<mem_loc_libc>'. Note that this time, the load fails. dmesg to see debug printk's.

Quoted 3 lines

Sounds to me that your kernel module will try to copy_from_user() from the user context of ... insmod :-)

Yeah, that's probably the problem (along with my lack of understanding how VM works -- heh). I guess I was just getting lucky with the 32-bit test in that the 32-bit libc was being loaded for my insmod process at the same virtual memory address as for my C test program.

Quoted 3 lines

You need to do your copy_from_user() from within the context of the program you try to access the memory from !

Can't do that in the "real" code I'm developing, so I guess I'll need to use get_user_pages. Hmmm . . . not quite as simple to use as copy_from_user, and I don't see any doc on it. But at least I've found a couple examples in the kernel tree.

Quoted 5 lines

If you need to access another context than the current one, you then need to use a different mechanism, such as get_user_pages(), though beware that you can only do that for memory, not SPE local store or register mappings.

The "real" code I'm developing is targeted at POWER, not Cell.

Thanks, Ben!

-Maynard

Quoted 4 lines

Ben.

#8 lore raw
Quoted 5 lines · Maynard Johnson
Quoted 3 lines

Sounds to me that your kernel module will try to copy_from_user() from the user context of ... insmod :-)
=A0=20

Yeah, that's probably the problem (along with my lack of understanding=20 how VM works =A0-- heh). =A0I guess I was just getting lucky with the 32-=

bit=20

Quoted 6 lines

test in that the 32-bit libc was being loaded for my insmod process at=20 the same virtual memory address as for my C test program.

Quoted 3 lines

You need to do your copy_from_user() from within the context of the program you try to access the memory from !
=A0=20

Can't do that in the "real" code I'm developing, so I guess I'll need to=

=20

Quoted 2 lines

use get_user_pages. =A0Hmmm . . . not quite as simple to use as=20 copy_from_user, and I don't see any doc on it. =A0But at least I've found=

=20

Quoted 1 line

a couple examples in the kernel tree.

Are you sure that this has to run in kernel space? You may be able to do the same thing with ptrace() from another user process, which is normally a lot easier to do, especially if you're not familiar with all the corner cases in powerpc linux memory management.

Quoted 6 lines
Quoted 5 lines

If you need to access another context than the current one, you then need to use a different mechanism, such as get_user_pages(), though beware that you can only do that for memory, not SPE local store or register mappings.
=A0=20

The "real" code I'm developing is targeted at POWER, not Cell.

Cell systems are compliant to the Power architecture and they run the same software, so you should at least make sure you have error handling in place to deal with an access of SPU local store pages and don't cause random crashes.

	Arnd <><
#9 lore raw
In reply to Maynard Johnson
Quoted 4 lines · Maynard Johnson

Can't do that in the "real" code I'm developing, so I guess I'll need to use get_user_pages. Hmmm . . . not quite as simple to use as copy_from_user, and I don't see any doc on it. But at least I've found a couple examples in the kernel tree.

We can help you if you tell us more about the "real" code.

Ben.

#10 lore raw
In reply to Maynard Johnson
Quoted 6 lines · Maynard Johnson

I'm developing a kernel module that needs to parse the in-memory ELF objects for a shared library (libc, to be specific). When running my test on a 32-bit library, it works fine, but for a 64-bit library, the very first copy_from_user() fails:

    Elf64_Ehdr ehdr;
    copy_from_user(&ehdr, location_of_lib, sizeof(Elf64_Ehdr);

Should be OK provided location_of_lib is a user address. I assume you know that copy_from_user returns the number of bytes *not* copied, hence a 0 return means success.

Quoted 2 lines

I talked this over a bit with Will Schmidt. He determined that access_ok (being done as a result of copy_from_user) was failing, but we

I suggest you print out the value of location_of_lib just to sanity-check it.

Paul.