Re: malloc(0) returns an invalid address

From: David Schwartz <davids_at_webmaster.com>
Date: Wed, 1 Dec 2004 07:49:54 -0800
> >>>>> On Tue, 30 Nov 2004 21:40:25 -0800,
> >>>>> "David Schwartz" <davids_at_webmaster.com> said:
>
> >> % ./a.out
> >> address of p is 0x800
> >> zsh: 794 segmentation fault (core dumped)  ./a.out
> >>
> >> Is this a malloc bug?  Or is this the intended behavior and the man
> >> page description is old?
>
> > 	This is the intended behavior but the man page description
> is correct. The
> > problem is not that the pointer is invalid but that you assumed
> that it was
> > large enough to hold a 'char' and it is not.

> I know the line of XXX is not correct; I simply tried to highlight the
> problem, but I seem to convey the real point.  How about this example?
>
> main()
> {
> 	char *p = malloc(0), *q;
>
> 	printf("address of p is %p\n", p);
> 	printf("the value of p is %c\n", *p);
> }
>
> the execution of this code would be like this:
>
> % ./a.out
> address of p is 0x800
> zsh: 645 segmentation fault (core dumped)  ./a.out

	This should fault. Although the return value of 'malloc(0)' is a valid
pointer, once you cast it to a 'char *', you cannot dereference it because
it does not point to a character. This same problem would occur with
'malloc(1)' and 'int *'.

> And, more specifically, my real-world problem is that 'ndp -r' fails
> when it calls sysctl without any IPv6 default routers.  The related
> code of ndp is as follows:
>
> 	int mib[] = { CTL_NET, PF_INET6, IPPROTO_ICMPV6,
> ICMPV6CTL_ND6_DRLIST };
>
>   (...)
>
> 	if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &l,
> NULL, 0) < 0) {
> 		err(1, "sysctl(ICMPV6CTL_ND6_DRLIST)");
> 		/*NOTREACHED*/
> 	}
> 	buf = malloc(l);
> 	if (!buf) {
> 		err(1, "malloc");
> 		/*NOTREACHED*/
> 	}
> 	if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), buf, &l,
> NULL, 0) < 0) {
> 		err(1, "sysctl(ICMPV6CTL_ND6_DRLIST)");
> 		/*NOTREACHED*/
> 	}
>
> The first call to sysctl sets 'l' to 0, since the list is empty.  Then
> the malloc returns '0x800' as a *valid pointer*.  But in the second
> call to sysctl, kernel rejects this pointer at line 1299 of
> sys/kern/kern_sysctl.c:
>
> userland_sysctl(struct thread *td, int *name, u_int namelen, void *old,
>     size_t *oldlenp, int inkernel, void *new, size_t newlen,
> size_t *retval)
> {
>
> 	(...)
>
> 	if (old) {
> -->		if (!useracc(old, req.oldlen, VM_PROT_WRITE))
> -->			return (EFAULT);
>
> and so we'll see
>
> % ndp -r
> ndp: sysctl(ICMPV6CTL_ND6_DRLIST): Bad address
>
> Note that the same code worked with, e.g., FreeBSD 4.10.
>
> So, if we wanted to call 0x800 "a valid pointer just with
> not-enough-size", it would be fine.  But then we need to implement the
> same logic in the kernel to provide consistent behavior.  (I would
> "fix" the malloc behavior though).

	The malloc behavior is not broken, so it cannot be fixed. The kernel check
semantics in 'useracc' are wrong for zero lengths.

	DS
Received on Wed Dec 01 2004 - 14:49:58 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:38:23 UTC