Banned C standard library functions in Git source code
github.com
github.com
I'm just kidding by the way. For those C programmers who haven't encountered these before, it is a powerful way to do a "goto" in C. Powerful in the sense that you can jump anywhere, not limited to the same function. If it's used at all these days, it's used for exception handling.
More info: https://en.wikipedia.org/wiki/Setjmp.h
setjmp/longjmp is not in this category, it might be argued to be easy to misuse but doesn't look innocent and is not likely to be used in normal codebases.
Let's take the example of PostgreSQL, which was mentioned in this thread: A potential implementation (I have no knowledge of pgsql internals) might be that for a single user session all memory is from a single arena, all locks and other handles are tied to the session handle, then it can be an efficient strategy to longjmp out on a critical error (i.e. IO error) and clean the arena as well as things tied to the session. instead of bubbling this up through all frames.
Funny way of saying "too error-prone for anyone sane to consider using".
I'm thinking there's a flaw in your mathematics...
PS: I'm not sure where he's getting his numbers though
It is just like letting programmer build a house. It is just a really bad idea.
> 70% of respondents say they are above average
1: http://www.libpng.org/pub/png/libpng.html 2: https://www.ijg.org
It might work great when you wrote it, but it might not even survive the next change to the codebase, and 10 years out, who is to know what programmers will come along and plug new stuff in without noticing the stack magic, etc
edit: another aspect of the same issue is that it can be difficult or impossible to write generic longjmp-safe code for many kinds of tasks. In C++ you have stack unwinding to e.g. delete large temporary heap allocations during a complex operation, but no such facility exists in C.
setjmp/longjmp will almost certainly require tracking any resource use carefully so that resources can be released at the setjmp point, when execution gets back there.
I mostly use it for terminating the recursive descent parsing on error. Otherwise it gets tedious to check return values everywhere in hand written code.
There is no reason libpng needed to use setjmp as part of its API, and that it does so is (I hope) widely regarded as a bad decision. It's "useful" only in so much as that is the API it defines, but there is no reason that this had to be the case, and simply returning an error code would have been very very much preferable.
It also has a stack managed in runtime. I'm not sure that would work for any other application than the fun example here.
https://pubs.opengroup.org/onlinepubs/7908799/xsh/ucontext.h...
int parse(void)
{
Token tok;
while ((tok = gettoken()) != END)
{
while (!shift(tok))
if (!reduce(tok))
return ERROR;
}
return ACCEPT;
}
I will say that any language without goto, or where the use of goto is discouraged, should at least provide guaranteed tail-call elimination as an alternative. In this case the gotos could easily be converted into loops, but not every algorithm is so accommodating.[0]: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.103...
What about co-routines?
One use of setjmp/longjmp I have used is in ZORKMID, to deal with the debugger. At the beginning of the execute() function I have:
while(setjmp(exception_buffer));
(The semicolon is correct; the loop body is supposed to be empty.) Normally, if you enter the debugger and then you exit the debugger to continue the execution, then it will continue from where it left off, which is likely in the middle of the execution of some instruction, and may result the same error again. But, if you change the program counter before continuing execution, then the debugger will keep track of that and will use longjmp instead when continuing execution, therefore skipping the rest of the instruction that stopped.You can program in C. C doesn't have try/catch...
And in all of the CPS code.
Here's a simple program to output a string with the characters in an unusual order.
#include <stdio.h>
#include <setjmp.h>
#define J(x,y) (longjmp(x,y),0)
int main(int argc, char **argv)
{
jmp_buf j[011];
int x,X=0;
signed char* i = " eehcef lo u ef'oYurls " +021;
(x=setjmp(j[X++]))?J(j[x],*++i):((x=setjmp(j[X++]))?J(x[j],*--i):((x=
setjmp(j[X++]))?J(x[j],(putchar(*i),*i+=128)):(x=setjmp(j[++X]))?((x<
0)?J(1[j],5):J(2[j],3)):(x=setjmp(j[++X]))?((x>>X)?J(1[j],6):J(1[j],3
)):(x=setjmp(j[++X]))?((x&0x80)?J(1[j],7):J(1[j],5)):(x=setjmp(j[++X]
))?(((X<<5)&x)?J(1[j],4):J(2[j],X)):(x=setjmp(j[++X]))?((x&128)?J(0[j
],X):J(2[j],4)):(setjmp(j[3])>=0)?J(j[2],6):0));
return 0;
}C is not a "try it and see" language. If you stray from the spec then things will act differently and break between compilers. Section 7.13.2 specifies that only local variables declared as volatile will have a determinate value after a longjmp.
1. The first time through this giant ternary defines 9 different small subroutines. Each subroutine can consider the value of "x" to be their argument. X is used as an index into the jmp_buf (subroutine) array during setup, but after that it's always just equal to 8
2. Subroutines 0, 1, and 2 are special. Their argument is the index of another subroutine to jump to. They mutate the character buffer state and then jump to the specified subroutine
3. Before jumping, subroutine 0 moves back a character, subroutine 1 moves forward a character, and subroutine 2 prints the current character and marks it by setting its high bit. Once a character is marked, it will never be printed again
4. All other subroutines get the current character value as their argument
5. Subroutines 3, 4, 5, 6, 7, and 8 all check, in different ways, whether the current character has been marked: x < 0, x >> 8, x & 0x80, (8 << 5) & x, x & 128, x > 0
6. Execution starts at subroutine 3
So you can annotate the code as:
// Subroutine 0:
// move to next char;
// goto x;
(x=setjmp(j[X++])) ?
J(j[x], *++i) :
// Subroutine 1:
// move to prev char;
// goto x;
((x=setjmp(j[X++])) ?
J(x[j], *--i) :
// Subroutine 2:
// print and mark (set high bit on) curr char;
// goto x;
((x=setjmp(j[X++])) ?
J(x[j], (putchar(*i), *i+=128)) :
// Subroutine 4:
// if (curr char is marked) {
// // goto 1(5);
// move to prev char;
// goto 5;
// } else {
// print and mark curr char;
// goto 3;
// }
(x=setjmp(j[++X])) ?
((x<0) ? J(1[j],5) : J(2[j],3)) :
// Subroutine 5:
// if (curr char is marked) {
// // goto 1(6);
// move to prev char;
// goto 6;
// } else {
// // goto 1(3);
// move to prev char;
// goto 3;
// }
(x=setjmp(j[++X])) ?
((x >> X) ? J(1[j], 6) : J(1[j], 3)) :
// Subroutine 6:
// if (curr char is marked) {
// // goto 1(7);
// move to prev char;
// goto 7;
// } else {
// // goto 1(5);
// move to prev char;
// goto 5;
// }
(x=setjmp(j[++X])) ?
((x & 0x80) ? J(1[j], 7) : J(1[j], 5)) :
// Subroutine 7:
// if (curr char is marked) {
// // goto 1(4);
// move to prev char;
// goto 4;
// } else {
// print and mark curr char;
// goto 8;
// }
(x=setjmp(j[++X])) ?
(((X << 5) & x) ? J(1[j], 4) : J(2[j], X)) :
// Subroutine 8:
// if (curr char is marked) {
// // goto 0(8);
// move to next char;
// goto 8;
// } else {
// print and mark curr char;
// goto 4;
// }
(x=setjmp(j[++X])) ?
((x & 128) ? J(0[j], X) : J(2[j], 4)) :
// Subroutine 3:
// if (curr char is not marked) {
// print and mark curr char;
// goto 6;
// } else {
// exit;
// }
(setjmp(j[3])>=0) ?
J(j[2],6) : 0));
// X = 8
// goto 3(0);
A more straightforward way of writing this would be #define IS_CURR_CHAR_MARKED() (*i < 0)
#define PRINT_AND_MARK_CURR_CHAR() do { putchar(*i); *i += 128; } while (0)
int curr_sub = 3;
for (;;) {
switch (curr_sub) {
case 3:
if (!IS_CURR_CHAR_MARKED()) {
PRINT_AND_MARK_CURR_CHAR();
curr_sub = 6;
} else {
return 0;
}
break;
case 4:
if (IS_CURR_CHAR_MARKED()) {
i--;
curr_sub = 5;
} else {
PRINT_AND_MARK_CURR_CHAR();
curr_sub = 3;
}
break;
case 5:
if (IS_CURR_CHAR_MARKED()) {
i--;
curr_sub = 6;
} else {
i--;
curr_sub = 3;
}
break;
case 6:
if (IS_CURR_CHAR_MARKED()) {
i--;
curr_sub = 7;
} else {
i--;
curr_sub = 5;
}
break;
case 7:
if (IS_CURR_CHAR_MARKED()) {
i--;
curr_sub = 4;
} else {
PRINT_AND_MARK_CURR_CHAR();
curr_sub = 8;
}
break;
case 8:
if (IS_CURR_CHAR_MARKED()) {
i++;
curr_sub = 8;
} else {
PRINT_AND_MARK_CURR_CHAR();
curr_sub = 4;
}
break;
}
}Outside of the current function, yes. Anywhere, no.
Longjmp allows you to return to places you’ve already been and have saved with setjmp. Also attempting to longjmp to a function that has since returned is undefined behavior. Thus longjmp’s usual use case is like exceptions in other languages, going back up the call chain, skipping intermediate functions.
So the solution is to setjmp before each call to an X function that might notice the connection is closed, and longjmp out of the error callback. Ugly, but it works.
These days, you should really just use XCB, or even Wayland.
I know there are a few different places that talk about how to use git's internal machinery, but not sure if any are specific to these banned functions.
For other codebases, snprintf is the usual recommendation, and careful straight buffer manipulation (mem*) iff performances are a concern.
[0] https://schacon.github.io/git/technical/api-strbuf.html
[1] https://code.forksand.com/linux/git_git/commit/7b03c89ebd103...
https://github.com/git/git/blob/master/strbuf.h
https://github.com/git/git/blob/master/strbuf.c
See this for the story of why strncpy/strncat are insecure:
https://en.wikipedia.org/wiki/C_string_handling#Replacements
[0]: https://www.youtube.com/playlist?list=PLrEMgOSrS_3fghr8ez63x...
or just use snprintf everywhere.
https://dwheeler.com/secure-programs/3.71/Secure-Programs-HO...
https://stackoverflow.com/questions/4007268/what-exactly-is-...
There is talk about npm/rust having massive dependency trees (and they do) because it makes taking on dependencies too easy. But I feel like C is on the opposite side of the spectrum, where managing dependencies is difficult so every C code base is rolling it’s own version of everything.
C also has an expansive standard library but it hasn’t offset re-invention of that stdlib in the ecosystem. I just see those implementations trapped inside code bases that don’t export them in a consumable way for other projects...
Personally I love writing C but rarely do because dependency management makes innovation in those code bases so time consuming for me.
One reason, I guess, is the diverse range of applications of C, another reason is the lack of advanced features like generics and templates.
But it would still be useful to create a simple library, intended for Unix-like platforms (beyond the BSD extension of stdlib). So it's more of a cultural problem?
However, here's the upshot-- that awful, buggy, custom solution is guaranteed to remain compatible with the codebase that contains it. You won't find a single instance where such a dev "improved", "refactored", "optimized", or "modernized" that awful, buggy code in a way that stopped the rest of the code from working and then shipped that broken blob of junk. And if you did find that, there's no way they'd convince the rest of the devs that this is a useful thing to do in the interest of "moving forward" or whatever.
On the other hand you'll find zillions of dollars spent on systems to keep dependency management tools from causing exactly that problem.
Just to give a real-world example-- a user reported that an abandoned C++ plugin wasn't working. We did a race:
1. Three devs try to get a single C++ dependency of that plugin to work cross platform (OSX, Windows, Linux).
2. I tried to port the C++ plugin to a C plugin with no deps.
By the time I ported, tested, and shipped, those three devs were tracking down a bug with the build script of the C++ dependency on Windows.
Edit: granted the plugin itself is only about 2000 lines of code.
>some subtle incompatible change
In statically typed languages this normally isn't an issue. Of course I'm aware that logic can also be changed, but in that case it's up to you to write appropriate tests (or just don't bump the versions of your libraries without a good reason).
while (*s1 && n--) {
*s2++ = *s1++;
}
*s2 = '\0';
it is, then.Also found https://github.com/mubix/netview/blob/master/banned.h on Github with a better list.
Code Red was the wake-up call for Microsoft and in February 2002, based on a memo from Bill Gates that first coined the phrase "Trustworthy Computing," Microsoft shutdown Windows development for the first time ever to get a handle on the security issues the products were facing.
https://www.itprotoday.com/strategy/story-behind-microsoft-s...
While Windows by all means still has its security issues, the toolchain is much more security oriented than most FOSS alternatives thanks to the Windows XP wake up call.
Android and ChromeOS are probably the mostly locked down alternatives on the FOSS space.
"Linux for Chromebooks: Secure Development"
Just know your tools...
File->New C++ Project, already there.
Available to everyone, regardless of their compiler switch mastery level.
Defaults matter.
Usually a bad omen. :)
Any use of non-const static variables in general has this problem, and strtok is just one example.
I’m constantly surprised that C isn’t specified in such a way that lack-of-reentrancy can be determined at compile time. You’d “just” need a symbol table, sort of like the debug symbol table, in each compiled object, holding for each visible symbol the set of function calls marked `__no_reenter` that are predominated by that symbol in control flow. (Yes, some functions do computed jumps, like with longjmp. Just implicitly label those functions __no_reenter unless the programmer explicitly labels them __reenter!)
Whereas with a term like reentry I think of two strtok frames on the stack at the same time, which is not possible.
I think it's a bit easier to conceptualize with a function that produces a simple return value in a static buffer. Take getpwnam(). It fills a global structure with info about a given user. So you may keep a pointer to that on your stack. Then you call some other function foo(). foo looks up another user. Suddenly you can't rely on that other call to getpwnam() not having overwritten the data in the result you got back the first time.
The same way you know a lot of other things about the codebase: By reading and understanding it. Given what strtok() is used for, it's almost always going to be working on a single context at any one time.
Second, code changes over time. Assuming you inspect 100 lines of code to ensure that they don't call strtok() ... do you sprinkle comments all over as a warning to future programmers not to add any calls to strtok()?
Finally, assuming we could evaluate all of the code in a program at once, it is impractical to build non-trivial programs that way. Instead, we rely on contracts. The idea is that if a caller conforms to its end of the contract, the callee will conform to its end. This allows us to reason about correctness of some code without reading all the other code in the universe and thinking through all the potential code paths. Any function using strtok() would be presenting a rather ugly contract clause "this function destroys strtok's static state". Even worse, such a clause would be contagious, infecting any client of that function, and so on.
Unless you have an insane gets implementation that writes the bytes backwards, you can also safely use it on untrusted input. Simply place a guard page at the end of the buffer. You can do the call from a forked child that you let die, taking notice of the problem in the parent. On systems without threading (because of locks in libc) you can do a longjmp to recover.
> Git Source Code Mirror - This is a publish-only repository and all pull requests are ignored. Please follow https://github.com/git/git/blob/master/Documentation/Submitt... procedure for any of your improvements.
I sort of get not allowing it, except that it's inconsistent.
If it is specific to one implementation, then it is not standard, pretty much by definition.
However, if you're more comfortable using GitHub PRs, there's a gateway interface at https://gitgitgadget.github.io/.
- strtok -> strtok_r / strtok_s
- asctime -> strtok_r / strtok_s
- gmtime -> gmtime_r / gmtime_s
- localtime -> localtime_r / localtime_s
- ctime -> ctime_r / ctime_s
- dirname -> dirname_r
- basename -> basename_r
- devname -> devname_r
- readdir -> readdir_r
- ttyname -> ttyname_r
- gamma -> gamma_r
- lgamma -> lgamma_r
- lgammaf -> lgammaf_r
- lgammal -> lgammal_r
- atoi -> atoi_l
- atof -> atof_l
- atol -> atol_l
- atoll -> atoll_l
- gets -> gets_s
- scanf -> scanf_l / scanf_s
- fscanf -> fscanf_l / fscanf_s
- sscanf -> sscanf_l / sscanf_s
- tmpfile -> tmpfile_s
- fopen -> fopen_s
- getenv -> getenv_s
- strdup -> strndup
- strcmp -> strncmp
- strlen -> strnlen
- (Multibyte/wide conversion functions without mbstate_t parameter)
- wcslen / wscnlen -> wcsnlen_s
- wcsncasecmp / wcscasecmp_l / wcsncasecmp -> wcsncasecmp_l
- strcasecmp / strcasecmp_l / strncasecmp -> strncasecmp_l
- bzero (use explicit_bzero, in some cases)
- calloc, realloc -> reallocarray (for arrays of non-byte items)
- memmove -> memmove_s
- strncat -> strncat_s
- strncpy -> strncpy_s
- srand / rand -> rand_r
There are others that are platform-specific. Thread-safety, internal mutable state (not thread-safe), and buffer-overflows are the primary concerns that aren't necessarily applicable in all situations.
[0] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm#mi...
/snark
Yes, C used have a cavalier approach towards security in the past. But if you're asking why the standard library is not fixed yet, I think that instead of pinning it to some lofty philosophy, it's safer to say that good C developers realized long ago that the original C strings are a mistake. Most big C projects define their own string functions and often their own length-prefixed string types. The C standard committee just gave up on fixing this issue in the standard library, but this is not due to philosophy, but because of the impracticality to force a standard solution on this stage.
Libraries like Qt with extreme lock-in are at the other end of the spectrum. If it works for them, that's nice. Doesn't work for me. I don't think using a string library is in the spirit of C programming.
Qt is as locked-in as any LPGL 3 FOSS project is.
No, we were talking about compatibility/modularity. You're shifting the topic.
> Qt is as locked-in as any LPGL 3 FOSS project is.
I'm not speaking about licenses lock-in, but about lock-in from an engineering point of view. Are you aware of any significant Qt projects that don't have "Q" all over their codebase?
(And yes, by contrast to GPL, I believe you can use LGPL libraries without suffering a terrible amount of (license) lock-in)
Compatibility/modularity doesn't happen in the air, rather in written code.
So either one passes structs around, and somehow they need to be compatible.
Or one passes pointer + lenght as two separated variables, with the consequences to keep in sync two unrelated variables, from the compiler point of view.
Roughly speaking C vars are either known fixed length, or accessed via pointer. There is nothing else.
(except arrays -- which are mostly pointers)
Plus added the fact that early C compilers generated quite lousy code on 8 and 16 bit computers, there is this idea to micro-optimize each line of code as it is being written, without any profiling feedback of it actually matters, rather cargo cult how writting code like X is faster than Y.
For example, outside 3D rendering and audio software processing, I never saw a visible impact (to the end user) of bounds checking.
Indeed the computation overhead on bounds checking is irrelevant for "cold" code, but consider this
1) Pretty sure you can get bounds checking when compilers detect that you're accessing a static array.
2) Otherwise, it's unclear how to devise a system that integrates bounds checking with C semantics. (Yes, that's unfortunate!)
3) Bounds checking does at least increase code size.
2) Solaris does it perfectly fine on SPARC thanks to tagged memory (ADI). Which Google in collaboration with ARM will make mandatory on future Android releases as well. [0]
3) It hardly mattered in MS-DOS and Amiga LOB applications developed in across Turbo Basic, Quick Basic, GFA Basic, Turbo Pascal, Clipper, so it matters even less nowadays unless we are speaking about PIC like hardware.
Regarding bounds checking I usually refer to Hoare's turing award speech, back in 1981:
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
Or for that matter, the DoD Multics's B2 security evaluation[1], with several remarks how PL/I made the system safer with its string handling, pointer integrity validation and bounds checking.
As noted, yes there are niche cases where bounds checking does have an impact, but for a large spectrum of code that gets daily written it isn't the case.
[0] - https://security.googleblog.com/2019/08/adopting-arm-memory-...
It is my understanding that 2) doesn't have anything to do with C (so given hardware support, you can have it for free whether working in C or not. I think this kind of invalidates your point).
And also, 2) is not perfect, only probabilistic (a source I found says 94% likelyhood to detect OOB).
And it works only for the most basic situations where you use the system allocator and never subpartition these allocations. So, beyond these simple cases that we can get for free without any involvement from C semantics, I still maintain that it is unclear how to devise a reasonable a useful system to do bounds checking that can be added to the C memory model. We can make up an annotation syntax to cover many of the simpler cases, but these are hardly better than plain assertions (which I regularly use).
I doubt Hoare had a good idea to add general bounds checking on a low-level language like C, otherwise that would be standardized by now.
In what concerns Solaris, and the requirements for future Android with ARM memory tagging, the system allocator is all there is, at least from official support point of view.
Hoare hardly needed to pursue such endevour, because all systems programming languages derived from Algol, like ESPOL, NEWP, PL/I, PL/S, PL/8, BLISS,.... were sane regarding bounds checking.
Hardware validation of memory is the only way to tame C, the alternative is to just dump the language, because as proven by ISO C11 dropping Annex K, very few actually care about making the language safe.
However given UNIX's dependency on C, it is also quite clear to me that in the next couple of decades C will be around, long after I am gone, and business opportunities to create companies on top of CVE exploits due to memory corruption bugs.
So, to restate, Solaris does not do it "perfectly fine". Thanks for making my point.
> In what concerns Solaris, and the requirements for future Android with ARM memory tagging, the system allocator is all there is, at least from official support point of view.
That's a pity, because if you're not doing your own allocators then you'll have to accept lock contention, extreme memory overhead (for smaller allocations, say <= 64 bytes), and you'll need to match every little allocation with a deallocation, instead of making e.g. custom pool allocators.
You're just not going to write a large infrastructure (i.e. performance-oriented) system in this way.
Why is strncpy insecure?
https://stackoverflow.com/questions/869883/why-is-strncpy-in...
> strncpy() doesn't require NUL termination, and is therefore susceptible to a variety of exploits.
I'm baffled by how some people claim strlcpy() is 'broken' or 'not safe' because it doesn't handle non-NUL-terminated inputs; the exact same thing applies to just about any function in the C standard library that takes strings as input. Are functions like strchr(), fopen(), printf(), strstr(), setenv() 'broken' as well?
But it's also a problem because maybe the programmer is using strlcpy to grab the first five lines of a 10TB memory mapped file. If you're not thinking about the implications of that return value it can be a real surprise.
It's not that people want to pass strncpy source buffers that lack NUL termination, it's that strncpy in certain situations will not NUL terminate its results.
https://begriffs.com/posts/2019-01-19-inside-c-standard-lib....
> some people claim strlcpy() is 'broken'
Speaking of strlcpy, it thankfully doesn't have the problem that strncpy does. However strlcpy is not in the C standard or in POSIX, so can't be used portably. In C99 snprintf is a better choice.
https://pointerless.wordpress.com/2012/02/26/strcpy-security...
strncpy pretends to be safe but fails to NULL-terminate and has other performance issues that generally lead me to believe it’s a security placebo. I would recommend using snprintf instead.
Edit: I guess some down voter doesn't believe me but it continues to be true. They are all string functions. Most of them do not take an output buffer size, so a source string exceeding the destination buffer will overflow. Others, like strncpy, take an output buffer size but will not null terminate when exceeded, so the buffer size must be reduced by 1 by the caller and potentially manually terminates, which is too easy to not consider. But any C programmer with significant experience already knows these pitfalls. So it's not controversial.
It seems useful to educate C programmers who don't have "significant experience" because, well, by definition, not every C programmer has "significant experience" and they are the ones who would benefit most from learning this.
It's not about being controversial: nobody's going to disagree with your explanation (it is what it is - it's not an opinion).
So I would say... What good is a verbose comment to explain? Somebody who doesn't instantly understand why it's banned can read the big warnings on the manpage, or they can google it and land on good explanations on sites like Stack Overflow, or they can look at the function signatures and come up with a correct guess. Then boom. They know. And from there they can assess other interfaces and see if they suffer the same weakness.
This ability tends to come from simple exposure.
Linkers already warn for this stuff. Doing it this way requires every source file to include banned.h, which I would guess is done by including it from some other common header, but that's not fool proof either.
They could rename them as “sorry X is unsafe and creates too much trouble” maybe, but in today’s internet “X considered harmful” is a common search query that everyone in the field is expected to know.
[0]: https://github.com/git/git/commit/c8af66ab8ad7cd78557f0f9f5e...
I'd assume there are no comments explaining this because for the intended audience, it is self-evident. "People browsing Hacker News on a Sunday" was probably not the intended audience :-).
Here are some of them:
- strcpy()'s rationale: https://github.com/git/git/commit/c8af66ab8ad7cd78557f0f9f5ef6a52fd46ee6dd
- strcat()'s rationale: https://github.com/git/git/commit/1b11b64b815db62f93a04242e4aed5687a448748
- strncat()'s rationale: https://github.com/git/git/commit/ace5707a803eda0f1dde3d776dc3729d3bc7759a
- sprintf()'s rationale: https://github.com/git/git/commit/cc8fdaee1eeaf05d8dd55ff11f111b815f673c58Git's banned.h roughly approximates the banned functions according to Microsoft's Security Development Lifecycle: https://docs.microsoft.com/en-us/previous-versions/bb288454(...
It seems Microsoft once published their own banned.h, but this file is not readily available from the MSDN anymore.
Microsoft seemed to have been adopting Git already, so it may be that acquiring Github was merely part of a broader Git strategy that Microsoft has adopted.
These tools have lists of functions not to use. Most of them — at least the security-focused ones — likely also include: strcpy, strcat, strncpy, strncat, sprints, and vsprintf just like banned.h
Suggestion: the macro should recommend a replacement.
“Sorry_sprintf_is_banned__use_FOO_instead”.
Edit: also interesting is a search for alloca: https://github.com/git/git/search?utf8=&q=alloca&type=
If the program declares or defines an identifier in a context in
which it is reserved (other than as allowed by 7.1.4), or defines
a reserved identifier as a macro name, the behavior is undefined.
It doesn't matter what the macro would expand to; simply defining the reserved identifier as a macro triggers undefined behavior. That doesn't mean it won't work, for some specific combination of standard headers, compiler, and program source code. It just isn't a strictly conforming program. A conforming implementation is allowed to flag this as an error, ignore the definition, or simply generate nonsense output.I'm surprised that "complicate audits" is given as a reason, because isn't this something static analysers (and I mean ones that actually analyse data/code flow, not dumb pattern-matchers) can easily detect? It's really just asking the question "how long is this/can this be" and following the data back to its origin(s).
Here is an example code to demonstrate the problem:
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "01234567";
strncpy(a, "foobar", 4);
printf("%.8s\n", a);
return 0;
}
Here is the output: $ cc -std=c89 -Wall -Wextra -pedantic foo.c && ./a.out
foob4567
A C89-conforming alternative I use is a macro like this that guarantees '\0'-termination as the first thing: #define strcp(a, b, c) (a[0] = '\0', strncat(a, b, c - 1))
An example from my code: https://github.com/susam/uncap/blob/master/uncap.c#L78-L87Here is how to use it:
#include <stdio.h>
#include <string.h>
#define strcp(a, b, c) (a[0] = '\0', strncat(a, b, c - 1))
int main()
{
char a[] = "01234567";
strcp(a, "foobar", 4);
printf("%.8s\n", a);
return 0;
}
Here is the output: $ cc -std=c89 -Wall -Wextra -pedantic foo.c && ./a.out
fooE.g. suppose we write something like
char* buf = strcp(((char*)malloc(4)), "foobar", 4);
expecting this to copy the beginning of the string "foobar" into a newly-allocated 4-byte buffer. Oops... this will actually call malloc twice (leaking the first buffer), and it won't have written the intended '\0' into the buffer that actually ends up getting used, so all bets are off...If strcp were a function, its first argument would be evaluated just once, and it would work as intended.
(See also https://linux.die.net/man/3/strncpy )
Here's a good explanation from Raymond Chen: https://devblogs.microsoft.com/oldnewthing/?p=36773
> Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.
No null-character is implicitly appended at the end of destination if source is longer than num. Thus, in this case, destination shall not be considered a null terminated C string (reading it as such would overflow).
Finding use of strlcpy in a program is a red warning of sloppy code.
There are good reasons it was kept out of glibc for so long.
The first step is to compile the C program with a C++ compiler. Next, start making improvements. If you skip the first step, the ceiling on improvements is limited.
And when it's not, the ceiling on improvements is limited.
And that's not saying that GCC extensions are not useful, but they should not be considered the norm
It wouldn't mean breaking things on compilers other than GCC. It would simply add compile-time checks when building with GCC, and would do nothing on other compilers.
But they cannot ban while (* p++ = * q++); now can they.
I was digging through its source code, and it turns out it isn’t memory-safe after all. Here’s the source code for stpcpy: https://github.com/ifduyue/musl/blob/master/src/string/stpcp...
If dest is too small in the function linked to above, musl’s stpcpy will happy cause a buffer overflow.
Don’t know how or from where I got the impression that musl was a “safer” alternative to libc.
musl is interesting for a variety of other reasons, but being more memory safe isn't really one of them.
For example, there's no way this can fail:
char s[10];
strcpy(s, "hello");
The problem is that it can also be used unsafely, and except in the simplest cases it's difficult or impossible to tell whether a give usage is safe.By contrast, gets() (which isn't even in the language anymore) is inherently unsafe, because you can't control what will appear on standard input.
I also have a (I guess?) Slightly unpopular opinion, that C is actually really ugly. Subjective, but C code is usually prone to repeating itself a lot or abstracting in an unsafe manner