Subject: Re: copy_from_user problem

From: Maynard Johnson <[email-masked]>
Date: Tue, 26 Feb 2008 08:49:28 -0600

Benjamin Herrenschmidt wrote:
> On Mon, 2008-02-25 at 19:47 -0600, Maynard Johnson wrote:
>> 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?
> 
> 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
> 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");