Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 1 Dec 2004 07:49:54 -0800
From:      "David Schwartz" <davids@webmaster.com>
To:        <jinmei@isl.rdc.toshiba.co.jp>
Cc:        current@FreeBSD.org
Subject:   Re: malloc(0) returns an invalid address
Message-ID:  <MDEHLPKNGKAHNMBLJOLKMEAKADAB.davids@webmaster.com>
In-Reply-To: <y7vfz2qpltw.wl@ocean.jinmei.org>

index | next in thread | previous in thread | raw e-mail


> >>>>> On Tue, 30 Nov 2004 21:40:25 -0800,
> >>>>> "David Schwartz" <davids@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



home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?MDEHLPKNGKAHNMBLJOLKMEAKADAB.davids>