Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 25 Apr 2013 10:57:29 -0700
From:      Jeremy Chadwick <jdc@koitsu.org>
To:        Guy Helmer <guy.helmer@gmail.com>
Cc:        FreeBSD Stable <freebsd-stable@freebsd.org>
Subject:   Re: FreeBSD 9: fdisk -It crashes kernel
Message-ID:  <20130425175729.GA10142@icarus.home.lan>
In-Reply-To: <257D766B-A296-43CD-A2B9-5F70A95A07A2@gmail.com>
References:  <80F41679-9C3A-4E61-8AAD-403410344C32@gmail.com> <20130425155818.GA8454@icarus.home.lan> <257D766B-A296-43CD-A2B9-5F70A95A07A2@gmail.com>

next in thread | previous in thread | raw e-mail | index | archive | help
On Thu, Apr 25, 2013 at 11:58:42AM -0500, Guy Helmer wrote:
> On Apr 25, 2013, at 10:58 AM, Jeremy Chadwick <jdc@koitsu.org> wrote:
> 
> > On Thu, Apr 25, 2013 at 09:06:49AM -0500, Guy Helmer wrote:
> >> Encountered a surprise when my disk resizing rc.d script caused FreeBSD 9.1-STABLE to crash. I used "fdisk -It ada0" to determine what the available size of the disk (which happened to be the root disk), and on FreeBSD 9.1 the kernel comes crashing down:
> >> 
> >> + fdisk -It ada0
> >> + /rescue/sed -En 's,.*start ([0-9]+).*size ([0-9]+).*,\1 + \2,p'
> >> vnode_pager_getpages: I/O read error
> >> vm_fault: pager read error, pid 65 (fdisk)
> >> pid 65 (fdisk), uid 0: exited on signal 11
> >> eval: arithmetic expression: expecting primary: ""
> >> Entropy harvesting: point_to_pointeval: date: Device not configured
> >> eval: df: Device not configured
> >> eval: dmesg: Device not configured
> >> cat: /bin/ls: Device not configured
> >> kickstart.
> >> eval: cannot open /etc/fstab: Device not configured
> >> eval: cannot open /etc/fstab: Device not configured
> >> eval: swapon: Device not configured
> >> Warning! No /etc/fstab: skipping disk checks
> >> fstab: /etc/fstab:0: Device not configured
> >> 
> >> Fatal trap 12: page fault while in kernel mode
> >> cpuid = 1; apic id = 01
> >> fault virtual address   = 0x0
> >> fault code                     = supervisor read, page not present
> >> instruction pointer      = 0x20:0xc0825fc4
> >> stack pointer               = 0x28:0xc5a088c8
> >> frame pointer              = 0x28:0xc5a08914
> >> code segment            = base 0x0, limit 0xfffff, type 0x1b
> >>                                     = DLP 0, pres 1, def32 1, gran 1
> >> processor eflags       = interrupt enabled, resume, IOPL = 0
> >> current process         = 91 (mount)
> >> [ thread pid 91 tid 100056 ]
> >> Stopped at  g_access+0x24: mlvl 0(%ebx),%eax
> >> db> where
> >> Tracing pid 91 tid 100056 td 0xc84c42f0
> >> g_access(c8481d34,0,1,1,0,…) at g_access+0x24/frame 0xc5a08914
> >> ffs_mount(c8481d34,c0d78380,2,c5a08c00,c829ae6c,…) af ffs_mount+0xf74/frame 0xc5a08a34
> >> vfs_donmount(c84c42f0,10000,0,c84cf200,c84cf200,…) at vfs_donmount+0x1423/frame 0xc5a08c24
> >> sys_nmount(c84c42f0,c5a08ccc,c5a08cc4,1010006,c5a08d08,…) at sys_nmount+0x7f/frame 0xc5a08c48
> >> syscall(c5a08d08) at syscall+0x443/frame 0xc508cfc
> >> Xint0x80_syscall() at Xint0x80_syscall+0x21/frame 0xc5a08cfc
> >> --- syscall (378, FreeBSD ELF32, sys_nmount), eip = 0x480d5feb, esp = 0xbfbfce1c, ebp = 0xbfbfd378 ---
> >> 
> >> I'll fix my script to not do this, but it seems odd that fdisk -It can make the disk "go away".
> > 
> > Please provide a full, unmodified copy of your script.
> > 
> > What's confusing to me is that after your sed call (which I don't even
> > understand, because it doesn't appear to be operating on anything except
> > stdin/stdout, and we don't know what that is -- again, show the script),
> > the kernel starts outputting indications that the root disk/filesystem
> > or its related metadata disappeared:
> > 
> >> vnode_pager_getpages: I/O read error
> >> vm_fault: pager read error, pid 65 (fdisk)
> >> pid 65 (fdisk), uid 0: exited on signal 11
> > 
> > Except the kernel stack trace indicates something called sys_nmount(),
> > which called vfs_donmount(), which called ffs_mount(), which calls
> > g_access().  All of those scream to me "someone tried to mount
> > something".  fdisk does not do mounting.
> 
> Right, which is why I copied the entire screen output -- it appears to me that the rc scripts had stumbled on until the kernel panicked.
> 
> > 
> > fdisk also shouldn't be writing to LBA 0 (the MBR) if you used -I -t.
> > I've been staring at fdisk.c for about 20 minutes now and I can't work
> > out a situation where -I -t would cause the MBR to be rewritten
> > actively.
> > 
> > The only GEOM calls I see in fdisk.c that would get called are
> > g_device_path(), g_open(), and g_close().  Actual device I/O uses read()
> > and write() (only in write_s0() which shouldn't be called).
> > 
> > Furthermore, GEOM has foot-shooting-prevention mechanisms in place (I'm
> > talking about kern.geom.debugflags) to keep LBA 0 from being modified.
> > Is your script setting that sysctl to 16/0x10 blindly?  Ahem.
> 
> No. The script is intended only to work for drives other than the one containing the boot partition.
> 
> > 
> > It would also help if you could state exactly what 9.1-STABLE source
> > you're using; if using svn provide revision (rXXXXXX), else provide
> > uname -a output.
> 
> rev 249788
> 
> > 
> > Finally: I would suggest using gpart(8) instead going forward.  This is
> > a separate recommendation though; if somehow I'm overlooking something
> > in fdisk.c where writes to LBA 0 really do happen, then that needs to
> > get fixed.  But gpart(8) is what you should use in general these days
> > anyway.
> > 
> 
> Seems like gpart was giving me some frustration with earlier versions of FreeBSD (7, I think) so I went with fdisk instead. Might work OK now...
> 
> I have included the full script below.
>
> { snipping for brevity; for reference, see this url: }
> { http://lists.freebsd.org/pipermail/freebsd-stable/2013-April/073234.html }

Thanks for this.

I could practically write a book on what's going on here.  Rather than
me spend hours of time reverse-engineering this, you're going to need to
step up to the plate and see if you can figure out what exactly triggers
the issue.

I will give you this analysis about fdisk -I -t:

When -I is specified, I_flag=1.

When -t is specified, v_flag=1, and also v_flag=1.

Function open_disk(), when fdisk is used with the -I option, will call
g_open() with the read-write flag set to 1.  Whether or not this
succeeds I don't know (and if it fails, but only with EPERM, then it
retries in read-only mode silently).  The -I flag correlates with the
I_flag variable (do not confuse this with i_flag):

 726 static int
 727 open_disk(int flag)
 728 {
 729         int rwmode;
 730
 731         /* Write mode if one of these flags are set. */
 732         rwmode = (a_flag || I_flag || B_flag || flag);
 733         fd = g_open(disk, rwmode);
 734         /* If the mode fails, try read-only if we didn't. */
 735         if (fd == -1 && errno == EPERM && rwmode)
 736                 fd = g_open(disk, 0);
 737         if (fd == -1 && errno == ENXIO)
 738                 return -2;
 739         if (fd == -1) {
 740                 warnx("can't open device %s", disk);
 741                 return -1;
 742         }
 743         if (get_params() == -1) {
 744                 warnx("can't get disk parameters on %s", disk);
 745                 return -1;
 746         }
 747         return fd;
 748 }

Variable fd is global.

After this call to open_disk(), read_disk() is used, but that's only
doing read operations on fd.

After this, the if (I_flag) code gets run.  This calls read_s0(),
reset_boot() (sounds ominous but isn't), and dos().

read_s0() does not issue any write I/O to fd, or call any functions that
issue write I/O.

reset_boot() just resets the in-memory-copy of the partition table.
It does not modify anything on disk.

dos() does not do any I/O at all.

At this point, if v_flag is set (which it is), print_s0() gets run.

print_s0() calls print_params(), which simply prints out the
in-memory-copy of C/H/S from the disk label and so on.  No file I/O is
done.  Once that's done, it calls print_part() on each partition,
which just outputs all the details -- again, no file I/O is done.

Finally, at this stage, if t_flag ISN'T set, then write_s0() gets run.
In this case write_s0() does not get called because t_flag=1.  FYI,
write_s0() is what does the actual write I/O to LBA 0/MBR.  After that,
exit(0) is called.

So even though -I -t calls g_open() with the read-write flag set, I
don't see anything that indicates writing to LBA 0/MBR happens.

So I do not see how fdisk -I -t could cause this situation.

fdisk -v, maybe, but again, you'll need to do the testing.

Now I have a question for you: how did you manage to get this output?

> >> + fdisk -It ada0
> >> + /rescue/sed -En 's,.*start ([0-9]+).*size ([0-9]+).*,\1 + \2,p'

Because this looks like /bin/sh -x output, but I need to know if that's
the case or not.

/bin/sh -x claims to echo commands to stderr ***before*** they're
executed.

So I'm then left wondering why we don't see output that equates to the
equivalent of this line:

    eval $(fdisk -v $DISK | $SED -En 's,.*start ([0-9]+).*size ([0-9]+).*,curroff=\1 currsize=\2,p')

Instead, we start seeing this:

> >> vnode_pager_getpages: I/O read error
> >> vm_fault: pager read error, pid 65 (fdisk)
> >> pid 65 (fdisk), uid 0: exited on signal 11
> >> eval: arithmetic expression: expecting primary: ""
>> Entropy harvesting: point_to_pointeval: date: Device not configured
>> eval: df: Device not configured
>> eval: dmesg: Device not configured
>> cat: /bin/ls: Device not configured

Your script has only 1 eval statement (and eval is very very dangerous.
I cannot stress this enough.  If you ever think you need eval in shell
scripts, you probably don't.)

Your script does not call df, dmesg, date, or /bin/ls.  So why are these
mentioned?  And "Entropy harvesting" comes from dmesg/the kernel message
buffer too, how is that ending up there?

Possibly the eval: error line only gets output by sh ***after*** all the
preceding [broken] stuff gets run.

But I'm also confused, because there isn't anything arithmetic-oriented
in your eval line, so why is it talking about arithmetic expressions?
You don't use expr either, so the only math operation comes BEFORE all
of that, specifically here:

    physsize=$(($(fdisk -It $DISK | $SED -En 's,.*start ([0-9]+).*size ([0-9]+).*,\1 + \2,p')))

My gut feeling here is that something "unexpected" happened and your
script went totally haywire as a result (probably some unexpected output
that got turned into something you didn't expect).  My favourite is
seeing asterisk/wildcards expanded to pull in all the filenames in $cwd.

I'm sorry to tell you, but there is a point when writing shell scripts
becomes unreliable/unmanageable/results in too much risk, and is time to
consider writing such things in an actual programming language
(preferably one without reliance on CLI tools, but real APIs).  I know
you don't need to hear that right now, but it's true.

See if you can work out exactly what line begins causing problems for
you.  My guess is that it's the result of fdisk segfaulting, but I'm
honestly not sure because the above output doesn't make entire sense.

Let us know what you determine/find out.

-- 
| Jeremy Chadwick                                   jdc@koitsu.org |
| UNIX Systems Administrator                http://jdc.koitsu.org/ |
| Mountain View, CA, US                                            |
| Making life hard for others since 1977.             PGP 4BD6C0CB |



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