Introducing OpenBSD's new httpd [pdf]
openbsd.org
openbsd.org
For sysadmins who closely follow the "recommended" way, having to migrate the configurations of the http server twice within half a year must have been a frustrating experience.
Also, I wonder what "removal from base" means exactly - can you still install them (the OpenBSD-patched versions) from the ports collection or something like that?
Not being in the base means that it doesn't get the same security attention as its not officially part of OpenBSD anymore.
Does "before" refer to the period before Apache was dropped from and nginx was imported to base? (Since apparently you can just use nginx from the base when it is there.)
EDIT: It just occurred to me that "complex setup" likely indicates that you would like to change some build flags, so you have to install from the ports. Stupid me.
Sysadmins had ~2 years to migrate from Apache to nginx (and if they didn't want to migrate, they could continue to use Apache from ports).
For nginx, they only had 6 months to migrate (though again, they can still use nginx from ports).
So no, they didn't have to migrate the http configuration twice a year, more like twice in 4 years, although, considering that OpenBSD only ever included into base 3 web servers, more like twice in 16 years.
Stack allocated buffers, questionable logic and a generally terrible style as well as a complete lack of comments.
Don't take my word for it, see for yourself:
https://github.com/reyk/httpd/blob/master/httpd/server.c
The "new" is a bit off too, the copyright runs 2006-2015.
There is nothing wrong with using the stack for what it was designed for.
The function names are self-explanatory.
The "style" might not be yours, but it doesn't make it bad.
And as described in the slides, it was derived from relayd.
Look longer.
Let me give you some examples:
Forward declarations for functions that could be avoided by re-arranging the code, using both 0 and -1 to indicate error returns from functions, bits like:
s = fd == -1 ? socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP) : fd;
if (s == -1)
goto bad;
Sure, I can read that but it takes more effort than it should and could be re-written much clearer and so on.> There is nothing wrong with using the stack for what it was designed for.
The stack should not be used to allocate buffers intended to hold data written by other routines or read from untrusted sources for reasons that have become painfully clear over the last couple of years.
> The function names are self-explanatory.
Strong disagree about the function names being self explanatory, plenty of the functions have non-obvious side-effects. The 'style' is asking for trouble and as for it being derived from 'relayd' that pretty much confirms that this isn't something new (which is actually a good thing), but an adaptation of something old to a new role (such adaptations are security wise something to be very wary of, re-purposing old code is a great way to find out what edge cases were missed previously).
Far more symbols are exported than necessary.
> The "style" might not be yours, but it doesn't make it bad.
There's a return at the end of a function returning void for no reason.
#if 0'd old code that should simply be purged.
In the server_log code I think there may be a path to get a double free of 'ptr' where it is used first in the block with the while loop, then not reset to NULL and re-used in the second block and freed if it is not NULL (which it still is from the previous block...).
The style used obscures this possibility.
s = fd == -1 ? socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP) : fd;
if (s == -1)
goto bad;
Is the equivalent of if (fd == -1)
{
s = -1;
goto bad;
}
else
{
s = socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP);
}
but with one less comparison, assignment of a constant to s in the error condition instead of a variable, which is usually faster, and a more clear, explicit, layout. Now, the compiler may make these optimizations for you, but it still leaves an uglier bit of code. Tristate operators can be useful, but in some cases, like this, they can make the code less efficient. if (fd != -1)
{
s = fd;
}
else
{
s = socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP);
if (s == -1)
{
goto bad;
}
}
or in rewriting the ternary function for clarity:
// If we have a socket, use it, otherwise, try to get one
s = (fd != -1) ? fd : socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP);
if ( s == -1 ) // we still don't have a socket, error out
goto bad;Ternary operators should, in my opinion, be put to the 3 am test. If you think you'd get confused at looking at code that uses one at 3 am, then you've not written it properly and should clarify.
I couldn't find any function that returns 0 on failure. Or do you mean a null pointer?
>The stack should not be used to allocate buffers intended to hold data written by other routines for reasons that have become painfully clear over the last couple of years.
You want to see a zero tolerance policy for them? I think this is a bit overprotective. Their maximum size is usually known and all called functions take a size argument (snprintf(), strftime(), strlcpy(), etc.). There's a chance one could make a mistake here, but C is the wrong language to dynamically allocate everything. The probability to forget to free it, including in every error path, is much higher in my eyes. But then again, a memory leak is probably preferable than a buffer overflow (of course, both are bad). There are also already many heap allocated buffers.
>Looking a bit longer at the server_log code I think there may be a path to get a double free of 'ptr' where it is used first in the block with the while loop, then not reset to NULL and re-used in the second block and freed if it is not NULL (which it still is from the previous block...)
I don't think so, but that code really is dodgy. Also, `ptr != NULL` is unnecessary (just mentioning since OpenBSD/LibreSSL developers like to mention that too).
server_socket_getport
An unknown address family will return a '0'.
> You want to see a zero tolerance policy for them?
No, but if you use that mechanism then it is preferable to have all the functions hitting that buffer to be visible from the scope of the declaration of the buffer. Passing it on to other libraries can cause problems when/if those libraries' maintainers mess up. So if you're defensive about this then you can't be hurt that way.
> I don't think so,
Agreed, looking still longer it looks like it will always end up with a NULL in it after the while, but that's very ugly to put it mildly.
> Also, `ptr != NULL` is unnecessary (just mentioning since OpenBSD/LibreSSL developers like to mention that too).
Yep. And there are plenty of other nitpicks like that but I don't even care that much about any of those, I mostly care about the way that the code is laid out making it an excellent place to hide some really nasty bugs.
I found this interesting article: http://daniel.haxx.se/blog/2014/10/25/pretending-port-zero-i...
So since 0 can be okay and (in_port_t)-1 (=65535) is okay, it has to be changed to return int and take a in_port_t* to be acceptable for you. The return value may not even matter if only AF_INET/AF_INET6 as family is possible - a comment could be of help here.
In this particular case, returning 0 doesn't necessarily indicate failure. Binding a socket to port 0 means you're asking the operating system to pick an available port for you, which one might argue is a reasonably safe default for unknown address families.
Let it crash, as close as possible to the point of origin of a problem is a very good principle.
There's a trick you can use to make remembering to free it more likely. Some people call it "RAII in C", I like to think of it as "nested allocations and errors": When you allocate something, or do something else which one, can fail, and two, must be undone (freed, closed, etc.), do the allocation and deallocation in properly nested pairs, and use gotos to jump to the code which undoes the last successful allocation if some allocation goes wrong. Basically, you arrange your code in a stack, where the most recent successful allocation is undone first, so you can neatly jump down to the code which undoes all successful allocations and doesn't try to undo the unsuccessful ones.
For example:
int foo(void)
{
int ret = 0; /* Variable we return to indicate
success or failure. Defaults to 0,
which is success. */
FILE *inf;
FILE *outf;
if ((inf = fopen("foo", "rb")) == NULL) {
ret = -1;
goto inf_fail;
}
if ((outf = fopen("bar", "wb")) == NULL) {
ret = -2;
goto outf_fail;
}
/* Do the actual work, now that you know you have
all the resources you need. */
fclose(outf);
outf_fail:
fclose(inf); /* If we jump here, we know we
successfully opened inf,
but not outf. */
inf_fail: /* If we jump here, we didn't actually
open squat, so all we can do is return
the status variable we set above. */
return ret;
}
C++ does basically this automatically, which is called RAII, but in C you have to do it by hand. int foo(void)
{
int ret = -1; /* Variable we return to indicate
success or failure. Defaults to -1,
which is failure. */
FILE *inf;
FILE *outf;
if (!(inf = fopen("foo", "rb"))) {
ret = -1;
return ret;
}
do {
if (!(outf = fopen("bar", "wb"))) {
ret = -2;
break;
}
do {
/* Do the actual work, now that you know you have
all the resources you need.
Use break if something fails.
*/
ret = 0;
} while(0);
fclose(outf);
} while(0);
fclose(inf);
return ret;
}
If you really must use gotos then just one suffices if you initialize all your
variables to NULL: int foo(void)
{
int ret = 0; /* Variable we return to indicate
success or failure. Defaults to 0,
which is success. */
FILE *inf = NULL;
FILE *outf = NULL;
if ((inf = fopen("foo", "rb")) == NULL) {
ret = -1;
goto foo_fail;
}
if ((outf = fopen("bar", "wb")) == NULL) {
ret = -2;
goto foo_fail;
}
/* Do the actual work, now that you know you have
all the resources you need. */
foo_fail:
if (outf) {
fclose(outf);
outf = NULL;
}
if (inf) {
fclose(inf);
inf = NULL;
}
return ret;
}
P.S. the return code of fclose() should also be checked, as I/O errors might only be reported on closeBut hey, why bother reviewing the code you call, the docs say that it is secure and that makes it so, right?
To trust the OpenBSD's code with regards to security is more of a safe bet than trusting a lot of other organization's code. But of course, that's a relative statement: maybe it's still a horrible assumption.
Given that and the fact httpd is made by people which know and care about the issues you point, there is much street cred to be had exhibiting a buffer overflow on the stack of this program.
""" OpenSSH is developed by two teams. One team does strictly OpenBSD-based development, aiming to produce code that is as clean, simple, and secure as possible. We believe that simplicity without the portability "goop" allows for better code quality control and easier review. The other team then takes the clean version and makes it portable (adding the "goop") to make it run on many operating systems -- the so-called -p releases, ie "OpenSSH 4.0p1". """
Obviously, I'm quoting from OpenSSH's page here, but I believe it's probably the same philosophy on why they're writing code that relies on the security properties of OpenBSD.
On a more flamebaity note, I don't know why you'd even want to write something like this in C. Writing a server for one of the most prevalent network protocols on the Internet in C, in 2015, just seems like masochism. This code reinvents so many wheels for the 1000th time in C code history, it's just tiresome to read. C++ would've reduced and simplified the code substantially and there are a growing number of other fine choices these days.
As the presentation says, it's based on relayd, so the code is not all new, a lot of it is reused.
For example, this setup would mean that a security flaw in the HTTP server that allowed a user to read memory would not be able to read any private keys used in the HTTPS server.
I guess some downsides would be some extra latency while the request is proxied, and some extra memory overhead for the second process.
I'm interested in anyones thoughts on this.
OpenBSD's TLS private key consuming daemons have moved to this model or are in the process of doing so. This helps to mitigate the problem of access to process memory results in disclosed private keys, also the requirement of the daemon's user facing bits to have access to the keyfiles.
There's very little latency added, it allows centralised logging and TBH apache is a ton more reliable than anything else out there. Does about 2-3 million requests a day.
Downsides include: Three sockets per client connection (this gets problematic around 1M client connections). Lack of information about the SSL negotiation in the http context. Stud doesn't have the typical graceful restart options that are typical with web servers.
On the plus side, stud is a lot less code than an http server, so its easier to modify things if you need to. I added sha-1/sha-2 cert switching for example. Would have been doable in an https server too, but a lot more to avoid.
https://github.com/reyk/httpd/blob/master/httpd/server_http....
http://www.openbsd.org/papers/bsdcan14-libressl/mgp00025.htm...
That's the entire joke, to jokingly annoy windows users.
So much passive agressiveness in such a fun way!
https://github.com/reyk/httpd/issues?q=label%3Afeaturitis+is...
featuritis tag in die bugtracker for currently denied features. Clearly aiming for as simple as possible while being useful.
Compression will change your server from being network throttled to being CPU throttled. And in this day and age, we can scale CPUs more easily than we can bandwidth.
> I add the label "featuritis" to remind us of extra features (eg. ldap) that we reject now but might want to reinspect later.
So it's not that httpd will never be extended beyond the basics, but these issues are simply out of scope right now. I like that approach.
Thanks!
It shows your server as sending only one certificate, the one with "CN=ns.ezequiel-garzon.net". It's missing the next one in the chain, "CN=StartCom Class 1 Primary Intermediate Server CA". I don't know the configuration details for the server you're using, but many servers use a separate "chain" file for the intermediates; if that's the case, you should put the main certificate in one file and the "StartCom Class 1 Primary Intermediate Server CA" in the other file.
And why it works in some browsers? Notice that Qualys listed the intermediate as "Extra download"; some browsers can download the intermediate certificate directly from the CA's web server. Some browsers cache the intermediate certificates they've seen, so if you've visited a properly-configured server with the same intermediate before, the browser will use the copy from its cache. But it's not recommended to depend on this; you should always include all intermediates.
cat your_cert.crt CA_cert.crt >> cert_bundle.crt[1] - http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/...
That's a bad choice in my opinion. Without reverse proxy functionality httpd can't match the flexibility of nginx.
Maybe reverse proxying is coming up next, as well as HTTP2. But once again, the goal is not to have a full-featured server that can replace Nginx. Rather something small, simple and secure.
https://calomel.org/relayd.html
So there's no need for httpd to do it as well.
calomel.org is filled with...ignorance, to say the least.
I didn't write this, but I agree with it - https://ef.gy/fastcgi-is-pointless
Yeah, FastCGI introduces a whole other attack surface, but it's on a trusted boundary, at least. Mixing trust levels within content seems like one of the primary classes of security problems.
For instance it looks like multiple occurrences of the same header will be ignored, and it will just close the connection instead of returning status 400 on protocol errors - in the best case.
Sure, you can try to implement just the parts of the HTTP spec that you think you'll need, and hope for the best. But wouldn't you rather use a simpler protocol that you could implement fully without having to worry about subtle errors sneaking in because you parsed the request line slightly wrong, or because your proxy decides to send two X-Forwarded-For headers instead of one.
If you're writing in Java or C++ you'll be able to find a mature HTTP library that handles all of this for you, but if for whatever reason I were writing from scratch, I'd take a limited-purpose protocol like FastCGI any day.
That said, closing the connection when you get rubbish input is generally a perfectly reasonable strategy, especially behind a reverse proxy that'll clean up after you. And in the code that is only done at all if asio.hpp claims that the underlying (tcp or unix) socket is in an error state. At which point it is impossible to reply.
As for repeated headers, according to http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 all multi-line headers must be representable as a single header by concatenating the parts with commas. So there was simply no use case for implementing this.
And FastCGI is definitely not simpler or "more limited-purpose" in any meaning of the word. At least not if you insist on implementing the full spec, which includes fun things like the authorisation and filter roles. And I highly doubt you can convince any conformant implementation to parse duplicate headers for you.
So implementing a reasonable subset - e.g. clean queries for HTTP or merely the responder role for FastCGI - is a perfectly reasonable strategy. And given all that, I will take a text-based protocol that in a pinch I can query against directly with a web browser or telnet any day.
I really hope this gets the portable treatment.
this is the sort of thing that makes me happy i'm no longer involved in the OpenBSD world. httpd & previously smtpd are two replacements that (in my opinion) have little additive value beyond existing, community-adopted solutions (e.g. nginx and postfix), diluting effort where it is needed.
does the world need a new httpd? maybe. but the world needs other replacement software to be done first because it'll have a greater impact.
for example, OpenBSD could invest time and effort in maturing static code analyzers to assist in code audits (especially of ports).
i suspect this new httpd was done less because it was needed and more because it could be done. that's the attitude i disagree with.
Nginx is simple, so, yeah – no need for a replacement there.
if nginx feels simple it's only because it doesn't copy a good chunk of its documentation into your /etc.
Nginx is approximately twice the size of the old Apache 1.3 based httpd OpenBSD had in base.
Nginx is an order of magnitude larger than the new httpd.
Perhaps nginx is simple from a user's perspective. But from a code/complexity viewpoint (think of all that must be read and verified to make sure it is correct, clean, simple and secure?) it is not quite so simple.
What makes you think that if I wasn't spending _MY_ spare time working on projects I like, I'd spend _MY_ spare time working on projects _YOU_ prefer ?
I work on projects because I need them and want to work on them, not because someone else feels I should do it. You say that you suspect a developer wrote code because it could be done and you disagree with that attitude, but I'd argue that there's much more to be said about the attitude of people thinking they are entitled to decide what _VOLUNTEER_ developers should do with _THEIR_ free time...
Besides, grabbing fixes just like that doesn't work so well once you've diverged enough and the upstream is constantly growing and changing. RTFA and you'll see this point discussed.
Forking nginx was actually discussed when a proposed nginx update diff was too large for proper review. Tons of complex regex parsing code was added with nobody willing to go through it all in detail.
The forking option was quickly dismissed. The tipping point happened when someone (reyk?) pointed out that relayd had most of the guts of a complete web server anyway, including OpenBSD-style privsep which has to be bolted on to virtually all software imported from elsewhere, including nginx. It took about a week from that discussion happening to having a relayd-based functional and peer-reviewed web server in base (+ a couple of months of shaking out a few bugs and adding minor features).
Why is there a webserver in base?
With OpenBSD, you can actually avoid the ports tree all together and still play a critical role in your infrastructure.
If you're going to distribute a system that doesn't do much of anything on its own, and delegate the responsibility of security (and choice!) to "the ports tree" you're not doing any better than Linux or FreeBSD.
Why is a web server included? It isn't needed by many people!
Because the developers want it. [1]
Because the developers want it. [1]
It was Microsoft of the 1990s that surprisingly brought some sanity to the table, with INI files. I'm glad tools like Git use this format.
"The httpd.conf configuration file is using OpenBSD's modern style of a sane cofiguration language, that attempts to be exible and humand-readable. It does not use a markup with semicola or tags, just english keywords and blocks identified by curly braces (\ fg"). This is commonly called the parse.y-based configuration within OpenBSD, because it originates from the grammar and parser that was written for pf."
https://github.com/rack/rack/wiki/%28tutorial%29-rackup-howt...
However, if your application's language is not suitable for use as a config language, then this becomes significantly more painful. You have to link in an interpreter for the configuration language, and then expose your application's internals to the interpreter. The easiest this could be is probably using JavaScript as a configuration language in Java, because the Java runtime already includes a JavaScript interpreter which can reach into the Java object model. The hardest it could be is using pretty much anything with C, where you'd have to add a separate interpreter, and then expose all the relevant internals by hand.