On Thu, 8 Oct 2015, Martin Natano wrote:
> Filesystem implementations depend on vattr_null() to initialize the 
> fields in struct vattr, which is true for all the fields except 
> va_filerev. It therefore is not set to VNOVAL as expected by the file 
> system, but contains whatever was there on the stack. This causes 
> VOP_GETATTR() on cd9660 and msdosfs vnodes to yield garbage for 
> va_filerev.

Thanks for the report.  Fixing that made me think more closely about 
vattr_null() and ended up totally rewriting it.


To back up...

Beware code that chains variable assignments to a potentially negative 
value, such as:
        foo = bar = baz = -1;

If foo, bar, and baz have different sizes, then the result may depend on 
the order!  To demonstrate:
        #include <stdio.h>
        int main(void)
        {
                unsigned long long llu;
                unsigned u;

                u = llu = -1;   printf("u\t%#x\nllu\t%#llx\n", u, llu);
                llu = u = -1;   printf("u\t%#x\nllu\t%#llx\n", u, llu);
                return 0;
        }

That program outputs:
        u       0xffffffff
        llu     0xffffffffffffffff
        u       0xffffffff
        llu     0xffffffff

showing that the order of assignment matters.  What's happening is that 
the value of an assignment is of the type of the lvalue being assigned to, 
so
        llu = u = -1;

is the same as
        llu = (unsigned)(u = -1);

Since (unsigned)-1 == 2^31-1 is already in the range of llu, there's no 
expansion to 2^63-1.  (I haven't verified, but assignment to an unsigned 
type to a smaller, signed type may count as signed overflow and 
technically be undefined behavior.  If so and your compiler optimized on 
that basis, beware!)


vattr_null() in the kernel was balanced on a knife's edge, switching among 
*six* types in the third statement here:

        /* XXX These next two used to be one line, but for a GCC bug. */
        vap->va_size = VNOVAL;
        vap->va_bytes = VNOVAL;
        vap->va_mode = vap->va_nlink = vap->va_uid = vap->va_gid =
                vap->va_fsid = vap->va_fileid =
                vap->va_blocksize = vap->va_rdev =
                vap->va_atime.tv_sec = vap->va_atime.tv_nsec =
                vap->va_mtime.tv_sec = vap->va_mtime.tv_nsec =
                vap->va_ctime.tv_sec = vap->va_ctime.tv_nsec =
                vap->va_flags = vap->va_gen = VNOVAL;

That has all of int, u_int, long, u_long, quad_t, and u_quad_t, and only 
works because it has a signed type on the right when assigning to a larger 
type.  It breaks if, for example, you swap va_nlink and va_fsid, or if you 
swap the tv_sec and tv_nsec order, or if you put va_size or va_bytes on 
the front of the third statement:
        vap->va_size = vap->va_bytes = vap->va_mode = ...

I suspect the "GCC bug" comment was because the author (pre-rev 1.1) 
didn't understand the C promotion rules.


So I've changed it to stop being fancy and just do the assignments one by 
one.  This also results in the assignments being done in memory order:
        vap->va_type = VNON;
        /*
         * Don't get fancy: u_quad_t = u_int = VNOVAL leaves the u_quad_t
         * with 2^31-1 instead of 2^64-1.  Just write'm out and let
         * the compiler do its job.
         */
        vap->va_mode = VNOVAL;
        vap->va_nlink = VNOVAL;
        vap->va_uid = VNOVAL;
        vap->va_gid = VNOVAL;
        vap->va_fsid = VNOVAL;
...etc
 

For this sort of init/reinit function, we may update that to use a
static const variable with the desired values and just assign from that,
but that'll just be gravy compared to making to code robust to changes
to typedefs.


Philip Guenther

Reply via email to