WEBlog -- Wouter's Eclectic Blog

Wed, 18 Apr 2012

Screen scraping sucks

At a customer, I've migrated a number of manually-maintained servers to having them be maintained through puppet not so long ago. Since then, some more machines have been added, and getting them up and running properly was a breeze: do a base install, install puppet, sign the certificate, restart puppet, and then wait and twiddle thumbs while puppet did its magic. Easy as pie.

Now, a few months later, we needed to install a number of windows machines for a lab (not my choice), and the person involved asked me to figure out some diskspace so we could start creating images for those.

Not a chance.

Instead, I suggested looking for a configuration management system, similar to puppet. Since we're using Samba 3 to run the Windows network here, dropping everything in Active Directory was not an option. But a short while later, he came back with the note that puppet, in its 2.7 version, actually does support Windows as a platform for the managed machines.

Interesting.

The unfortunate bit was that puppet supports creating files and installing software when it is distributed as an MSI file, but not when it's distributed as a .exe file. This is not unexpected; MSI files can be installed noninteractively; but when something is distributed as a .exe file, it means it needs to be installed interactively; and puppet does not have the ability to interact with GUI software.

The workaround: use something that does have that ability (in my case, autoit), and use an exec block in puppet to make it call those scripts. In effect, that's a bit like screenscraping. Add a creates stanza to the block, so that the installer isn't started again if the software at hand has already been installed. This 'autoit' thing also comes with a recording utility, allowing one to create an initial script by just doing the installation, and having the tool just record stuff.

With that, the machines are installed 99% automated. I say 99%, because there are still some issues:

I'll have to think about this some more, I guess. First, it's clear that while puppet does have some Windows functionality, it's not entirely ready yet. And somehow, using autoit to add to Puppet functionality feels like an ugly hack.

We'll see what the future brings.

Fri, 13 Apr 2012

On PHP

This dude nails it. Well, almost—can't say I agree with the python bit. But other than that, yeah, pretty much what's wrong with PHP.

(Add that to what google seems to consider my most popular bit of code, ever, and, well, hmpf).

Fri, 23 Dec 2011

Static program analysis with LLVM and clang

"Static program analysis" is a technique whereby a program is verified for errors without actually running it. Finding bugs manually with a debugger and one's brain is tedious, so every shortcut that can help you avoid having to do so is great.

There are some commercial tools available to do such analysis, some of which are rather expensive; but there are also some open source tools available to do similar things. One of these is built into clang, the C compiler of the LLVM project.

Using it is fairly simple. Instead of compiling something with 'make', compile it with 'scan-build make'. This will set the CC (and similar) environment variable(s) so that before the compiler is ran, the clang static-analysis checker is run over the very same source code. The output of this checker is an HTML file with your source, but with comments added to explain the bugs which the tool found.

What does that mean? well, let's look at an example, shall we?

The iframe above contains one out of three reports produced by a scan-build run over the nbd source code (if there's no iframe, someone scrubbed some HTML in your RSS reader. Just follow the link just above instead). The other two are 'dead assignments', which might mean that I'm currently depending on some undefined behaviour (which would be bad), or it might mean that I'm being overly cautious (which makes my code future-proof, which would be good), or it might mean something else—I still need to investigate. But this one is pretty interesting.

In the example, there are eight, numbered, comments in the source code. The first seven show the code path which scan-build took through my code before getting at the eighth comment; and the eighth is where things go bad. In this case, when going through the function as shown, we have a NULL pointer dereference.

When looking at the scan-build output, it's important to realize a few things. First, the code path shown may be just one of a number of possible code paths. For instance, the null pointer dereference would still happen if the phase function parameter would not contain the NEG_INIT bit with the client pointer set to NULL. However, clang does not show these other code paths; this is presumably an optimization ("if we've already shown that this kind of bug is possible at a particular location through one code path, don't bother recording future instances of that very same bug at the exact same location through another code path"). This means that sometimes, some of the branches shown may be completely irrelevant to the bug at hand. In this particular case, in fact, it's possible to show the bug with just the eighth comment; the first seven are in fact totally irrelevant.

Second, the fact that the clang static analyser found a bug does not mean that it's possible to crash the application. Yet. In this particular case, the negotiate function will never be called with the NEG_INIT or NEG_MODERN bits not set, and with the client parameter set to NULL. That's an implicit assertion; there are a few ways in which this function may be called, but the client parameter may only be NULL if NEG_INIT and NEG_MODERN are both set at the same time.

Since nbd-server doesn't currently call the negotiate function in that way, it is not currently possible to crash the server by exploiting this bug. But that doesn't mean it won't ever be possible, nor that there isn't a bug in the code. We may assume that the above rules are true, but we never check it. Adding an assertion to that effect should make sure that no future change to the code will accidentally introduce that error and cause a NULL pointer dereference.

Is this a silly and useless precautionary measure? Not really. Usually, bugs happen in code not because someone wasn't thinking straight, but because there's so much going on inside a piece of software that it can be too much for any programmer to remember. If a function assumes that its parameters are within a given subset of all possible states, but does not check that this is in fact true, then when (not if) some future change incorrectly introduces a state that is outside of the assumed states, things will break. And that's Bad(TM).

Wed, 05 Jan 2011

Substituting gdb debug paths

Gdb is a debugger with many, many, many(1) options. Some of them are rather obscure, others... less so.

While it's always had the ability to read source files and do useful stuff with them, recent versions of gdb have gained another interface (which you get by calling "gdb -tui") that makes it a bit easier to watch the source as you step through the code. Additionally (and I don't know how long that feature's been there), you can do some interesting things with source paths. Such as substituting a source path prefix by another prefix -- useful if you've moved sources around. This is accomplished by way of the 'set substitute-path' command:

(gdb) set substitute-path /home/buildd /home/wouter

Now whenever gdb is told by the debugging info to search for a file /home/buildd/foo/bar/baz.c, it will instead search for /home/wouter/foo/bar/baz.c. Very useful.

One problem with the above approach, however, is that you need to know what the original path is—the /home/buildd in our example. If you've built the binary yourself, you just need to remember where you built it, and you're all set. But what if you haven't built the binary yourself? I used to use strings for that, and look for any likely candidates, but that isn't very reliable and requires a bit of hunting through many many lines that just happen to look like strings to what really is a fairly naive program. Additionally, it doesn't work when the debugging symbols are in a separate file (as implemented by dh_strip), amongst others.

But no worries! There's a simple solution. Rather than using strings, use readelf -w instead, and grep for DW_AT_comp_dir, which will give you the correct source directory to substitute.

I'm happily single-stepping through a library now. If that isn't nice.

1 no really, many

Sat, 16 Jan 2010

ACCEPTED

My mutt said this last night:

 894   + Jan 15 Archive Adminis (0,4K) ipcfg_0.1_amd64.changes ACCEPTED

This obviously means that if you wish to use it, you no longer need to go through git; you can just add experimental to sources.list and run 'apt-get install ipcfg'. A few notes, though:

And in case you wonder why the hell I went from 0.1 to 0.3:

ipcfg (0.2) experimental; urgency=low

  * Rebuild without .git directory. D'oh.

 -- Wouter Verhelst   Tue, 12 Jan 2010 17:43:09 +0100

srsly

Wed, 13 Jan 2010

Using ipcfg, now

Yesterday, I had some time to debug ipcfg some more. The main blocker for me to upload it to unstable was the fact that I could not get WPA security to work; therefore, I could not use it myself. In the interest of "eat your own dogfood", I did not feel that uploading some experimental code to Debian that I'm not using myself is a good idea.

That problem is now fixed, albeit through something of a hack, one that I hope will not be necessary forever: I decided to write a plugin to run ifupdown extension scripts (found in /etc/network/if-*.d). It does require some set-up, and there are still some severe issues; but as of now, I am using ipcfg rather than ifupdown on my laptop.

Those interested in trying it out can either wait for ftp-master to ack the upload and then install from unstable, or just fetch it from the git repository.

Tue, 29 Dec 2009

Perl on the N900

Since about a week, I have a Nokia N900. They're not actually on sale in Belgium yet, but I managed to get hold of one through a grey market shop.

The device is quite nice. I do have a few irks with the interface here and there, but Nokia has a public bugzilla that I'll surely use to file bugs whenever and wherever appropriate. And after using it for about a week, I thought of migrating this small application which I once wrote on half an afternoon using perl, postgres, and Gtk2, to the device. It's mainly a cataloguing system that I store my DVD collection in (which has gotten large enough that this is necessary), nothing earthshattering. The ideal candidate to get my feet wet in developing for the N900, so to speak.

That effort got stopped dead in its tracks pretty quickly.

Nokia-N900-42-11:~# perl
use Gtk2;
Can't locate Gtk2.pm in @INC (@INC contains: /etc/perl /usr/local/lib/perl/5.8.3 /usr/local/share/perl/5.8.3 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.8 /usr/share/perl/5.8 /usr/local/lib/site_perl .) at - line 1.
BEGIN failed--compilation aborted at - line 1.
Nokia-N900-42-11:~# apt-cache search perl
perl - Larry Wall's Practical Extraction and Report Language.
libpcrecpp0 - Perl 5 Compatible Regular Expression Library - C++ runtime files
po4a - tools for helping translation of documentation
libpcre3 - Perl 5 Compatible Regular Expression Library - runtime files
libgdbm3 - GNU dbm database routines (runtime version)
perl-base - The Pathologically Eclectic Rubbish Lister.
liblocale-gettext-perl - Using libc functions for internationalization in Perl

Or, in other words: they only ship the perl bits needed to make dpkg run.

Hrmpf. This would make stuff slightly more... involved.

Oh well.

Sun, 20 Dec 2009

Re: Grrr

I received a number of comments on my "Grrr" post, all of which missed the point:

Yes, I am aware that there are many more ways to fix this issue beyond a memcpy. However, the example code is legal and would not crash the application, if not for the fact that libc thinks I am doing something wrong. On top of that, this kind of overflow "protection" only kicks in when the code is compiled with -O2 rather than with -g -O0. While I am not sure whether the difference is due to the absense of debugger symbols, or rather due to the different optimization levels, fact is that software which runs fine in debugging should also run fine in production.

There are good arguments for compiling all C code with -Wall -Werror, and I do that as a matter of course for all C software that I write. However, sometimes automated tools are just wrong in their compile- or run-time bug detection, in which case such it should be possible to disable that detection. This is one such case, and my blog post was more about ranting about the inability to do so, rather than about the fact that I had to memcpy when in fact there were other options available.

But yeah, perhaps I should have been clearer about that. Forgive me for not being clear after having fought with compilers for far too long.

Wed, 16 Dec 2009

Grrr

struct {
	char str[4];
	char sc1;
	char str2[3];
	char sc2;
} foo __attribute__((packed));

snprintf(foo.str, 5, "%04X", data);
foo.sc1 = ';';
snprintf(foo.str2, 4, "%03X", otherdata);
foo.sc2 = ';';

Yes, I know that both snprintf() calls in the above snippet will overflow their immediate buffer. Yet this code is safe; the network protocol for which this code is written does not actually need nor expect NUL bytes; instead, it wants semicolons. I could of course use a "char foo[9]" rather than a struct as above, but I find this to be slightly more convenient than to count offsets.

However, this code does not work with glibc, because the buffer overflow detection kicks in.

Solution:

char buf[5];

snprintf(buf, 5, "%04X", data);
memcpy(foo.str, buf, 4);

In other words: add a stupid and useless memcpy, because someone thinks they're smarter than me. Stupid morons.

Fri, 18 Sep 2009

Announcement

I was really happy a few years back, when Martin announced he'd start working on netconf. Not just because I agreed with his assessment that ifupdown needed replacement; also because I had been thinking about a good way to implement such a thing, and had been planning to start writing 'soon'. With Martin's announcement, I made a few suggestions to give him some input, and then put my plans away—I had more important things to take care of, anyway.

Unfortunately, netconf did not manage to reach the ambitious goals that Martin set out, mainly due to lack of contributors. I wanted to help out; I really did. But Martin's choice of python to implement a 'prototype' which then had to be reimplemented in C or C++ didn't get me very thrilled. Not just because I have a severe dislike of python; also because implementing something twice is, in my humble opinion, not quite the best way to do something like this.

But hey, who am I, if I don't put my code out there.

Never the less, I tried helping. I really did. During debconf8 at Mar del Plata, I approached Martin with the suggestion that I start reimplementing those bits that were unlikely to change anymore in C, as a first step toward something that could be in the base system. Unfortunately, that didn't happen. After trying for about two weeks, I just gave up. I thought I could read python code and reimplement that so that it would work the same way, but I couldn't. I thought I could implement python modules in C without knowing python, but as it turns out that's a laughable idea.

For several months then, I didn't think about it anymore. In March of this year, however, I got a little bored. And what does one do when they get bored? Right, you find something to do. I my case, that was 'figure out how bison works'. I'd of course known about parser generators for quite a while, but just never had the time to dive in deep and figure out how they work. When I needed to do a config file for nbd, I instead hand-wrote my parser and used a lexer from libglib. But I wanted to learn how to do things properly, so I sat down.

Long story short, when I had to choose something somewhat more complex to implement, I thought 'how about a hypothetical config file for a network configuration utility', and I started implementing that. It turned out to be fun, so I didn't stop after I knew enough about bison. And just last week, for the first time I successfully used it to bring up my wired interface using DHCP -- and bring it down again. It can also already manage static interfaces through netlink, though it isn't quite able to the equivalent of 'ip addr flush' yet.

Fun.

The system is quite flexible. It has a plugin interface (which needed some refinement that I did using some help on IRC just today), which should allow developers to implement extra functionality by just dropping a shared object in the right directory. In fact, I'm working on the wireless support (hence my blog post of last week) as a plugin.

Obviously the code isn't remotely ready yet. The wireless code has a long way to go. The firewall module needs to be started. It will probably crash and burn if put to the test. But even so, it already has one feature that ifupdown, in its 10 years of existence, never acquired: it will not try to DHCP off an interface if it finds out that there is no cable connected to it—unless you wrote a configuration that asks you to do so.

I wanted things to be as simple as possible, and therefore the minimal valid configuration file that will do something useful is the following:


That's right, it's empty. This will cause the system to bring up the 'lo' interface at bootup, and do nothing else. If you say 'ifup eth0', where eth0 is a valid and existing interface, it will first check whether there is a link, and if so, DHCP off of it.

There's much to do still, but I have now reached a point where I feel the system is ready to meet all challenges it should be able to meet (or so I hope), and where it might be nice if interested people check it out.

The code is in a git repository that's mirrored on git.debian.org, github.com, and my own git.grep.be. The latter is my --mirror repository, but is not available for cloning to anyone else. If you're interested in checking it out, you may want to start with doc/tech.txt, and/or examples/ipcfg.cfg. I updated the former just yesterday, so it's quite up-to-date. On the other hand, while the latter shows the direction I would like the system to take, the code is a far cry from implementing everything shown there, and it has been a while since I last checked that file out, so the comments in there are, in some cases, somewhat outdated. But that's good, because I want to keep the file as a reference of the goals that I originally had, before I'd written this whole corpus of code, so that I can go back at some undefined point in the future and fix things.

Comments are (more than) welcome.