LWN.net Logo

Advertisement

Advanced thin client solution for Linux, based on Open Source. Mix Windows and Linux applications on the same desktop.

Advertise here

Buggifying critical core modules

Posted Mar 19, 2008 14:33 UTC (Wed) by dw (subscriber, #12017)
In reply to: Buggifying critical core modules by quotemstr
Parent article: Who maintains dpkg?

$ cat -n a.c ; ./a ; uname -sp
     1  #include <stdio.h>
     2  
     3  int main(void) {
     4      printf("%d %d\n", sizeof(int), sizeof(void *));
     5      return 0;
     6  }
4 8
NetBSD x86_64


Same for Linux amd64.


(Log in to post comments)

Buggifying critical core modules

Posted Mar 19, 2008 14:44 UTC (Wed) by quotemstr (subscriber, #45331) [Link]

No.

sizeof(int) and sizeof(void*) are likely to be different, but sizeof(void*) and
sizeof(any_other_t*) are the same on every POSIX platform I know about. That's the distinction
without a difference the OP mentioned. You don't need to pass "exactly the right type"
because, AFAICS, the bit pattern of the pointer is going to be same no matter what -- provided
you pass an actual pointer.

Buggifying critical core modules

Posted Mar 19, 2008 15:15 UTC (Wed) by dw (subscriber, #12017) [Link]

Oh, whups :) In that case I do not know the answer.

Buggifying critical core modules

Posted Mar 19, 2008 15:19 UTC (Wed) by tjc (subscriber, #137) [Link]

> the bit pattern of the pointer is going to be same no matter what --
> provided you pass an actual pointer.

This took me by surprise:

int main(void)
{
        int n;
        void *p = &n;
        printf("%p\n", p);
        p++;
        printf("%p\n", p);

        return 0;
}

I thought incrementing a void pointer would cause a compiler error (or at least a warning),
but it seems to treat it as a char pointer and increment it by one byte on my system (gcc
4.1.2 on Solaris).

Buggifying critical core modules

Posted Mar 19, 2008 15:26 UTC (Wed) by dw (subscriber, #12017) [Link]

A GCCism:

     -Wpointer-arith
         Warn about anything that depends on the ``size of'' a
         function type or of "void".  GNU C assigns these types a
         size of 1, for convenience in calculations with "void *"
         pointers and pointers to functions.

Buggifying critical core modules

Posted Mar 20, 2008 18:34 UTC (Thu) by dododge (subscriber, #2870) [Link]

The lesson being: when you run "gcc" with no options, it does not compile the C language, but
rather the "GNU C" language.  GNU C bears a strong resemblance to C but has many additional
and alternate semantics.

If you're trying to write portable code and you want gcc to follow Standard C rules, you have
to explicitly request it.  I normally use, at a minimum:

-std=c99 -pedantic -Wall -Wextra

Buggifying critical core modules

Posted Mar 19, 2008 16:47 UTC (Wed) by jengelh (subscriber, #33263) [Link]

#include <stdio.h>
#define E(t) printf(#t " = %u\n", sizeof(t));

int main(void)
{
        E(void *);
        E(void far *);
        E(void near *);
        E(void (*)(void));
}

near pointers are always 2, and far are always 4. But it is interesting to see that untagged pointers vary in size, depending on the compiler model. Turbo C++ 1.x for DOS says:

/* “Tiny” and “Small” model */
void * = 2
void (*)(void) = 2
/* “Medium” model */
void * = 2
void (*)(void) = 4
/* “Compact” model */
void * = 4
void (*)(void) = 2
/* “Large” and “Huge” model */
void * = 4
void (*)(void) = 4
/* For your information about this compiler, in all models: */
int = 2
long = 4

nonfunctionpointertype * is always the same width as void *, though.

Buggifying critical core modules

Posted Mar 19, 2008 17:07 UTC (Wed) by quotemstr (subscriber, #45331) [Link]

Not applicable. DOS isn't a POSIX system.

Buggifying critical core modules

Posted Apr 7, 2008 8:00 UTC (Mon) by jengelh (subscriber, #33263) [Link]

So what? You do not need DOS to make a compiler output far/near-based pointers. What it
requires is — I think — that the CPU is in real mode because that's where far/near pointers
make most sense.

Buggifying critical core modules

Posted Mar 19, 2008 16:53 UTC (Wed) by zlynx (subscriber, #2285) [Link]

It would be a perfectly valid POSIX system to have 32-bit function pointers and 64-bit data
pointers, for example.

In that case, void* would have to hold 64 bits, but a varargs function expecting a function
pointer and passed a void* would pull 32 bits off, leaving 32 bits of junk for the next
argument to pull.

The reason this bug is so subtle is because systems like that are quite rare.  Up until they
aren't, and then the waste product hits the rotary impeller.

When AMD64 became popular, I ran into *many* of these vararg bugs on my Gentoo system because
developers had become used to passing 0 for NULL.  To my shame, I patched many of these by
passing NULL and only now realize it should have been (char*)0 or similar.

Buggifying critical core modules

Posted Mar 19, 2008 17:15 UTC (Wed) by quotemstr (subscriber, #45331) [Link]

I don't think your scenario is realistic. dlsym() returns a void*, not a function pointer
type, implying that void* and function pointers are the same size. Plus, IIRC, POSIX
explicitly guarantees that function pointers can be round-tripped through void*.

I don't think we have to worry about different pointer sizes any more than we have to worry
about bytes that aren't eight bits wide. Don't litter your code with casts; just use NULL.

Buggifying critical core modules

Posted Mar 19, 2008 17:41 UTC (Wed) by zlynx (subscriber, #2285) [Link]

C guarantees void* will be large enough to hold any pointer type and automatic type-casting
handles most of the problems.

The *only* places this causes problems is functions without prototypes, which no one does
anymore, and vararg functions.

Since the only place this makes bugs is vararg parameters, it is a very subtle problem indeed.

Another thing that makes these bugs painful:
When I was debugging my AMD64 Gentoo problems caused by passing 0 instead of NULL or (char*)0,
the problems only showed up *sometimes* with -O2 compiles and *usually* with -O3, but never
with -O0.  It entirely depended on what junk happened to be on the stack after the terminating
0 integer and if the junk was 0 or not.

Buggifying critical core modules

Posted Mar 19, 2008 17:48 UTC (Wed) by quotemstr (subscriber, #45331) [Link]

I'm not saying that you should spell "null pointer" at "0", but that it is okay to use NULL
(which should be 0L or ((void*)0) everywhere.

Buggifying critical core modules

Posted Mar 19, 2008 18:04 UTC (Wed) by zlynx (subscriber, #2285) [Link]

I should have kept my response simple.

No, you cannot use NULL everywhere.  If function pointers and data pointers are different
sizes, a void pointer will be the largest size.  This will cause problems with varargs when it
expects a pointer of the smaller size.

An example with 32-bit function and 64-bit data:
After calling the vararg function *properly* the stack might look like:
 (char*)0xf00fdeadf00fdead
 int(*x)(int,int)0xdeadbeef
 (int)32
 (int)64

But if you call it with NULL for the function pointer it will be:
 (char*)0xf00fdeadf00fdead
 (void*)0x0000000000000000
 (int)32
 (int)64

Now if the vararg function pulls the arguments like this:
 pull char* - 0xf00fdeadf00fdead
 pull int(*x)(int,int) - 0x00000000
 pull int - 0
 pull int - 32

See how the last two int arguments got the wrong data?

Buggifying critical core modules

Posted Mar 19, 2008 18:09 UTC (Wed) by quotemstr (subscriber, #45331) [Link]

Even in the highly unlikely scenario that function pointers someday become _smaller_ than data
pointers (which would have no benefits and which would make dynamic loading exceedingly
complicated and delicate), the vast majority of pointers are still data pointers, all of which
will have the same size.

My point is that in the real world on mainstream systems, you don't have to worry about
contingencies like the kind you mentioned anymore. Embedded, special-purpose code is
different, but then we're not talking about POSIX anymore, and lots of other assumptions go
out the window too.

I don't imagine dpkg is going to run on a microcontroller.

Buggifying critical core modules

Posted Mar 20, 2008 18:53 UTC (Thu) by dododge (subscriber, #2870) [Link]

> If function pointers and data pointers are different
> sizes, a void pointer will be the largest size.

No, as noted above C explicitly requires void* to have the same representation and alignment
as char*.  The guarantee that a pointer can be reliably converted to void* and back again
applies only to pointers to object and incomplete types, not pointers to functions.

Buggifying critical core modules

Posted Mar 20, 2008 8:58 UTC (Thu) by filipjoelsson (subscriber, #2622) [Link]

But isn't 0 an integer? In which case it would be necessary to cast that 0 to a ptr. Ok, so there's not much point arguing about which pointer to cast it to - but removing the cast would still be a subtle error.

Buggifying critical core modules

Posted Mar 20, 2008 11:14 UTC (Thu) by BenHutchings (subscriber, #37955) [Link]

Another varargs type problem: sizeof() returns size_t, but %d expects an int. You should use
%zu to format values of type size_t.

Buggifying critical core modules

Posted Mar 20, 2008 11:58 UTC (Thu) by dw (subscriber, #12017) [Link]

Hah, I was waiting for that. :)  Seems you can't complain about type widths and then go write
an invalid printf!

Buggifying critical core modules

Posted Mar 20, 2008 23:54 UTC (Thu) by nix (subscriber, #2304) [Link]

... unless you want your program to be portable to any of the large number 
of extant systems that don't support %z in printf(). (Linux/glibc systems 
have supported it for ages, of course, but it's the only system I'm 
writing code for right now that does.)

Copyright © 2008, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds
Powered by Rackspace Managed Hosting.