Taking Down Tim Hentenaar

There is a blog post by Tim Hentenaar that says that people should not read my book, Learn C The Hard Way. It has the title “Don’t Learn C The Wrong Way” and it asserts that I am teaching C the wrong way, with a few examples as to why. The problem with Tim’s post, is that Tim actually doesn’t know how to teach much of anything, and is completely uninformed of the security defects that his own code has. In fact, Tim successfully demonstrates that he is actually a beginning coder who has no business telling others how to code. In this blog post I will simply take down Tim’s supposedly expert opinion by using his replies to me in an email exchange where he demonstrates his lack of understanding, and then tries to cover for it in the most laughable way.

First, let’s establish how much of an expert Tim thinks he is, and what he’s advising you, my reader to do:

“Recently, I came across an e-book written by Zed A. Shaw entitled Learn C The Hard Way, and while I can commend the author for spending the time and energy to write it, I would NOT recomend (sic) it to anyone seriously interested in learning the C programming language. In fact, if you were one of the unlucky souls who happened to have purchased it. Go right now and at least try to get your money back!”

That’s a very serious condemnation of my book, especially from someone who has never taught C, never written a book, can’t even spell “recommend”, and later demonstrates that he doesn’t have a clue about security defects inherent in C. So what are Tim’s complaints about my book?

Tim Has No Teaching Experience

The majority of his complaints about my book, Learn C The Hard Way stem from a lack of understanding in my (very successful) teaching method. To Tim, and most old school programmers, the way to teach something is to teach all of the topic at once in one huge chunk. You teach Make by writing a chapter on Make that tells the reader every single little thing about Make possible, and then demonstrate with some code. Here’s Tim’s statement to that effect:

“At this point, the only thing I can think is, “I’d just love for you to show me a damn working Makefile!” A novice will be thinking, “What the hell’s a Makefile?” as the concept of a Makefile has not yet been introduced.”

Then later he says:

“I don’t know how to set-up my environment, this “Makefile” thing pulled a Jimmy Hoffa, and now I have to use this Valgrind thing, after I go download it and build it from source. Great…”

The problem is, Tim didn’t read far enough to where I do explain how to make an environment, and misunderstood my purpose at that point in my book. I’m not teaching the reader to write a Makefile and start a project. I’m teaching them to quickly get their very simple C code to compile. My target readers are people who have a language like Python or Ruby but haven’t dealt with a compiled language before. But to Tim, this is insufficient because he thinks a beginner is like him and needs to know all of the Make to be able to use it.

This lack of understanding of an actual beginner is exactly why so many programmers are so terrible at teaching, or even writing basic software for non-developers. It’s not that a programmer is somehow emotionless or a “robot” like obnoxious nerd haters say. It’s that the majority of programmers have a far more advanced understanding of computing, and specifically the software they create. Through their path to that understanding have forgotten what it was like to be a beginner. This leads them to assume many things that just aren’t true. Such as, “Unless a beginner is taught every single aspect of Makefile construction they cannot use Make’s implicit build rules to build a basic C file.”

This means that Tim’s statements about how I teach are mostly invalid because he doesn’t understand how people learn to code. He’s never had to teach someone who’s just starting out so he thinks blasting them with a treatise on Makefiles is what they need 4 exercises into a course of study. By contrast, I actually sit with real people and have them go through my books, and then adapt the exercises based on where they get stuck. I also used to have comment sections on every page to gather information on how to improve exercises. Tim basically read K&R and wrote some crappy C code, which we’ll see shortly.

However, Tim’s rabid and obnoxious condemnation of my book isn’t his actual opinion. In private emails he says this:

“I don’t doubt the seriousness of your offer. In fact, one of my colleagues also read my article, and he and I were discussing it this evening, and he told me that he’s a fan of your writing style, and would love to see you write a really good book on C.”

Tim doesn’t believe my book is entirely irreparable and a failure as he states, and in private he says there’s only a few problems with it. He even offered to help me make it better despite his lack of experience writing or teaching. What he actually thinks is I should write it the way he would write it, then it’d be a good book for you to buy. Despite Tim’s complete lack of qualifications in programming, writing, education, or anything other than having a blog, he thinks that his opinion is so superior that I should rewrite my book to fit his ideas of education, not a student’s model of learning based on actually sitting with readers and helping them.

This kind of arrogance and hubris leads me Tim’s largest failing in his post, this code right here:

void copy(char from[], char to[], size_t n)
    size_t i = 0;

    if (!from || !to) return;
    while (i < n && (to[i] = from[i]) != '\0')

    to[n] = '\0';

Tim’s claim is that this function here is superior to a function I had written called “safercopy”, but it has a critical buffer overflow that he actually attempts to defend in the most laughable way.

To understand Tim’s failure you need to see my original “safercopy”:

int safercopy(int from_len, char *from, int to_len, char *to)
    int i = 0;
    int max = from_len > to_len - 1 ? to_len - 1 : from_len;

    // to_len must have at least 1 byte
    if(from_len < 0 || to_len <= 0) return -1;

    for(i = 0; i < max; i++) {
        to[i] = from[i];

    to[to_len - 1] = '\0';
    return i;

What sends most C coders into a tizzy about this code is it came from a thought experiment I was doing where I did code analysis on the K&R C book (the book by the authors of C). Many programmers took this as an offense to them (so rational), and so they would focus on how I said this function here (safercopy) was better than a similar string copy function in the K&R C book. The problem is, to discredit my claims that mine is better, they would play this little semantic shell game:

  1. “Your function is vulnerable to Undefined Behavior (UB) just like the K&R function.”
  2. They then write some example that uses a totally different UB from the hundreds available, not the buffer overflow UB from a malformed C string.
  3. Then proclaim that, since both functions are vulnerable to UB, my claim of mine being safe (notice, not safER), are invalid.

This is a lot like you buying a new lock for your front door that’s really great, so you tell your friend about it. Your friend goes, “Pfft, your lock is no better than leaving your door open, I could totally break into it.” Your friend then shows up with a SWAT team battering ram and smashes the door in like butter and says, “See? Your lock is pointless. Just leave your door open.” You, and I, aren’t saying a better lock is completely foolproof and perfect. We are saying it is safer, not totally safe. Doors are easily bashed in using countless methods, right down to setting your house on fire. When we talk safety of the lock, we mean against lock picking compared to the other lock. To say I should leave my door open because there’s a thousand ways to get into my house is insane.

However, my function is more resistant to a common externally accessible vulnerability. This is something I would love to research, but UB has different levels of exploit surface that is accessible to an attacker from outside the running process. A C string is fairly trivial to clobber so that it is missing the ‘\0’ terminator. It’s a bit more difficult to make random pointers go wherever you want, but still possible. It’s nearly impossible to rewrite the C code for a running process to cause a math error and make a compiler skipped a portion that was considered UB. When studying the security of C code we tend to just assume all UB is the same and don’t make this distinction of accessibility to an attacker. Bad C coders then use this UB to simultaneously defend bad code (“All code is breakable with UB”) and condemn other’s code (“Haha, you’re triggering UB”).

When I say my function is safER, I do not mean it is totally invincible. That is impossible in C, and one of the reasons I tell people to not use C anymore. I now firmly believe that C is impossible to write securely and is designed with flaws that are irreparable, mostly because of the huge number of UB that can easily be triggered externally.

I mean that the code in this simple function protects against this one buffer overflow that is often externally exploited, while the original K&R code does not. That’s all.

Which leads me to Tim’s lack of understanding of his own code. Clearly, he thinks his code is even safer than mine, but if you look at it again:

void copy(char from[], char to[], size_t n)
    size_t i = 0;

    if (!from || !to) return;
    while (i < n && (to[i] = from[i]) != '\0')

    to[n] = '\0';

You’ll see that he only has one size, so if that size is invalid for the to variable then you get a buffer overflow. Here’s a trivial demonstration of it:

#include <stdio.h>

void copy(char from[], char to[], size_t n) { 
    size_t i = 0;

    if (!from || !to) return;
    while (i < n && (to[i] = from[i]) != '\0') {
        printf("to[i]=%c, i=%zu\n", to[i], i);

    printf("i=%zu, n=%zu\n", i, n);
    to[n] = '\0';

int main(int argc, char *argv[])
    // thanks to @mistahzip for pointing out this 
    // is a better demonstration code
    char to[] = {'A','A','A','A'};
    char from[] = "XXXXXX";

    copy(to, from, 6);

    printf("Final byte is: %x\n", to[3]);

UPDATE: I had my original analysis wrong and I apologize for that. This is a better demonstration of the problem, and a new analysis showing the buffer overflow.  Thanks for @mistahzip for setting me straight and putting up with me being an asshole.  Just goes to show you, this shit is hard.

Tim’s code works as long as the strings are valid, however it’s incredibly common for C strings to be invalid, and that’s how you get the buffer overflows from C strings.  In this example, I’ve added printing so you can see what’s going on.  I use a malformed to array so that you can see, if it’s wrong then it gets overwritten with garbage.  In addition, he does to[n] which will always set the wrong byte if from is larger than to. Any C coder worth their salt would realize this, and in many ways this is worse than even the K&R version since it is more complicated.

When you do this on many systems you just get a bus error of some sort, but not all. Many times you’ll have the end of one string still be inside a valid region of memory, and operating systems aren’t even close to foolproof on protecting buffer overflows. If you’re using a system that allocates stacks on the heap (such as in greenthreads), then you’ll typically blast right past this variable and into another function’s code. That’s very dangerous and creates remote code execution vulnerabilities.

You may be thinking, “Yeah but I could write code that breaks your safercopy too!” Yes, like I said, C has so much UB it’s an entirely unsafe language and you can destroy anything. The point though is that this is an insanely common and trivial programming error that is just bad math for one parameter. Mine you have the size for both so you don’t make this error as easily. You can still make the error, but it’s harder than with Tim’s. With Tim’s you’ll make this error all the time.

Arrogance and Hubris

I told Tim about this really silly error in his blog post and did he do the right thing and at least admit publicly that I demonstrated a trivial error in his code? Nope, not only has he not updated his code, further demonstrating that he doesn’t know what he’s talking about at all, but he proceeded to defend his code with the most asinine of defenses:

“That’s why strncpy() / strlcpy() were written, but of course with all such things, there’s a performance penalty to pay. Even with length checking, it’s still possible to trigger UB, for example via integer promotion (i.e. strncpy() with a negative length, which I did point out) or having dest and src overlap. … It’s much harder to carry out a buffer overflow attack with SSP, DEP, and ASLR these days. Although there are always ways around the best intentioned restrictions.”

His function, in his own words, isn’t wrong because, again, you can use a totally different set of UB to cause problems so this easily externally accessible one isn’t a problem. And there’s also strncpy/strlcpy, so his function is still valid (what?). Oh, and also there’s, like, uhhh oh SSP and DEP that totally protect against these problems (even though they don’t and we see it all the time). These are the words of someone stumbling to still be right to protect their ego, and demonstrates Tim’s lack of intellectual honesty and integrity.

Tim Is An Unqualified Beginner

This is your classic defense from an arrogant programmer who refuses to admit that he actually doesn’t know what he’s talking about. When I receive complaints that my code isn’t working, even if it’s been run through the ringer over and over, I still go and double and triple check that it’s working. If Tim had sent me this kind of trivial defect I would have fixed my code and worked to find out why I caused the error. To programmers like Tim, who think they know C but are totally clueless about computer security, it’s inconceivable that his code could be wrong.

This is a sign of a beginner. A beginning programmer assumes his code is right even in the face of all evidence to the contrary, like Tim does here. They defend it to the end, because they are personally attached to their creation and not objective. An expert assumes his code could be wrong at any moment and adds as many defenses as possible. This shows that you should not listen to Tim about C coding, and definitely not learn anything from him. He is entirely unqualified and should be ignored.


Tim Hentenaar wrote a confused screed about my book being terrible and claiming nobody should buy it. However, his expertise is completely lacking to make that determination, his code has defects in it, and he arrogantly refused to admit that it had problems. He also defends his security defects with confused logic about UB and the existence of other functions that have nothing to do with his own code. Listening to Tim about how to learn C is therefore a dangerous thing to do. No book is perfect, and let me tell you that first printing of mine had loads of problems, but until Tim writes a better C book you’d do well to ignore his advice and him.

In fact, this is the problem with the majority of the detractors from my book. None of them have written books, and many of them don’t even code C or have C in production. Writing books and teaching people is incredibly difficult, much more difficult than hanging out in IRC yelling at beginners about Undefined Behavior or writing blog posts. Over this next week I’m going to systematically take down more of my detractors as I’ve collected a large amount of information on them, their actual skill levels, and how they treat beginners. Stay tuned for more.