oss-sec mailing list archives

Re: How to do secure coding and create secure software


From: Dan Cross <crossd () gmail com>
Date: Mon, 29 Sep 2025 12:49:04 -0400

On Mon, Sep 29, 2025 at 11:51 AM Amit <amitchoudhary0523 () gmail com> wrote:
On Mon, 29 Sept 2025 at 01:21, Jeffrey Walton <noloader () gmail com> wrote:
On Sun, Sep 28, 2025 at 10:53 AM Amit <amitchoudhary0523 () gmail com> wrote:
On Sun, 28 Sept 2025 at 03:11, Solar Designer <solar () openwall com> wrote:
You claim that "If functions/methods are secure then the whole software
is secure."  If we talk C where main() is also a function, and limit the
definition of "whole software" to one program, then I'd agree - your
claim can as well directly say "if [all functions including] main() are
secure then the whole software [meaning this one program only] is
secure."  While true, under those definitions this isn't a useful claim.

However, if in "functions/methods are secure" you refer only to smaller
building blocks, then no, the program built from them may still be
insecure.  Also "the whole software" isn't necessarily just one program.

[...]
But the point is that this is what people have said and this is all theoretical.

Can someone give an example as to how a software made up of secure functions can be hacked?

Authenticated Encryption.  You might have a module that performs AES
encryption, and another module that performs MAC'ing using SHA-256.
But if you combine them incorrectly, you have an insecure system.  If
interested, the way to combine them so they are provably secure is
Encrypt-then-Authenticate (EtA) as used in IPSec.

And the counterexamples... The way SSH combines them is insecure, and
the way TLS combines them is insecure.  SSH and TLS combine them in a
way that sets up an oracle.  In crypto engineering speak, SSH and TLS
are _not_ IND-CCA2.  (IND-CCA2 is a strong notion of security).

And it gets worse.  Some developers use encryption alone -- they do
not provide an authentication tag.  That is, the developer completely
omits the MAC step.  So all cipher texts are vulnerable to tampering.

Also see Hugo Krawczyk's paper "The Order of Encryption and
Authentication for Protecting Communications" (2001),
<https://www.iacr.org/archive/crypto2001/21390309.pdf>.

To sum it all up, people have raised two points:

1. Secure functions can't secure the software if the logic inside the function is not secure or if there is a bug.
2. Wrong ordering of secure function calls can lead to insecure software.

The above point number 1 is already addressed in my article. I will copy paste the lines that handle this issue:

=======================
2. The function body should also be secure. After writing code, you should
review your code for security issues and also get it peer reviewed for security
issues. In general, you should always get your code peer reviewed for security
issues, bugs, company coding guidelines, etc.
=======================

So, it looks like people didn't read this point.

No, people read it, but from the replies I have seen, no one (who
responded, anyway) was particularly convinced of the veracity of the
statement.

So, now point number 1 is addressed.

That's not addressed.  You may _think_ something is secure, it may
even _be_ secure in a specific context. But that doesn't mean that it
_is_ secure, or will remain so if it is, should the surrounding
context change.

Also, if I say that a function should be secure then it definitely implies that the whole function should be secure 
and the function body should also be secure.

If the function's body is not secure then we can't call it a secure function.

As has been pointed out, this is a tautology. But as advice for actual
programming, it is reductive to the point of uselessness.

If a function's body has bugs that will lead to hacking then obviously we can't call that function a secure function.

Secure function means secure function in all respects. I don't understand why people are thinking that a secure 
function can have bugs, insecure logic, etc.

Perhaps it doesn't.  But such a secure function does, generally, not
exist in isolation; things _around_ the "secure function" may render
the overall system insecure.  For example, there was a time when DES
was considered secure; now it's not. So a function that used DES and
was considered "secure" 40 years ago is no longer secure.

If someone tells me that a function is secure then I will assume that the function is secure in all respects - 
checking arguments, no bugs in body, etc. and I have already made this clear in point 2 of my article.

Define "bugs in body", though.

For example, in your original email, you mention validating the length
of a 0-terminated C string. You suggest that one may use the `strnlen`
function to do this, since there's no guarantee that an input buffer
actually contains a 0 terminator (i.e., it may not be a string).
Further you say, "For example, you can specify that the minimum length
of a string argument should be 1 and the maximum length of the string
argument should be 1024." You then write, "The code will be 'len =
strnlen(str, 1025); if (len == 1025) { return error; }'".  Well, now
we have an ambiguity; C defines "the string" as containing the 0
terminating byte (cf eg C18, sec 7.1.1 para 1: "A string is a
contiguous sequence of characters terminated by and including the
first null character"). `strlen`, on the other hand, returns the
number of characters before the terminating zero, which is not the
same thing.  Did you really mean the maximum length of the string, or
did you mean its size?

Suppose that you truly meant that the maximum length, e.g., as would
be returned by `strlen`, should be 1024; then that string's _size_
could be up to 1025, as for a maximally sized string the terminating
null character would be the 1025'th char. But this code returns an
error on the value 1025; clearly this rejects a maximum length string.

Reading between the lines, it appears what you mean is that the
string's maximum _size_ is 1024, as the code clearly intends to find
the terminating zero within the first 1024 characters, which would
yield a maximum length of 1023, not 1024.  But in that case, you're
looking at up to 1025 characters, one beyond the size of the string:
consider what happens in the case of a pointer that points to 1024
bytes of validly mapped memory, but those 1024 bytes end on a page
boundary, and the subsequent page is unmapped.

In either case, your example code appears to exhibit a classic
off-by-one error, and can be tricked into either looking beyond the
end of a valid memory object (if max len == 1023 and max size ==
1024), or failing to properly accept valid strings (if max len == 1024
and max size == 1025). The error here is in assuming that the return
value of `strnlen`, as you have used it, is enough to robustly
establish that the string ends within the acceptable bounds.

Sure, this is easy enough to fix in this case (hint: read up on the
`memchr` function). But beyond that simple error, C provides you with
_no way_ to determine whether a given `char *`, when provided as an
argument to an arbitrary function, points into a valid object of your
arbitrarily chosen length. So already your advice is impossible to
follow in the general case.

Now, coming to the above point number 2:

An example of openssh is given that it first does authentication and then it does encryption and this is insecure. I 
will investigate this and reply later.

Another area you don't touch on at all are TOCTOU bugs.

I suggest, perhaps, studying a bit more.

        - Dan C.


Current thread: