Simple Dynamic Strings library for C
github.com
github.com
The README on this repo is awesome. Opening up with advantages and disadvantages? Awesome. Plenty of code examples covering all of the major use cases? Awesome. Quick overview of the internals? AWESOME! Quick two line note about how to use the library in your project? Awesome.
I'm tempted to rant about how I wish documentation was taken more seriously, and that programmers seem to make it a point of pride that spending the first half hour with a library figuring out how to actually use it is just something we have to deal with as programmers, but I won't do so aside from this single sentence.
They almost never have examples(Is that some sort of rule?), they have no "quick summary of the shit you use 95% of the time", and they get generally written as a novel, seemingly from the perspective of the developer of the util rather than consumer.
Mind you, I have used man pages many times before, but only because it was the best source of information, not because it was a particularly efficent one.
The Markdown README of this string library is a thousand times better than any man page I have seen.
Near the end there can be an examples section, and many man pages have one. If I run "man 3 open" I see a reasonable examples section.
I like manpages because I'm already editing or running programs in the terminal, and I can pop open the man page in the terminal quite quickly and conveniently, without a search online or even a single network request, without reaching for the mouse, etc.
INFO pages on the other hand, are terrible. Is there something less intuitive than the Info reader?
Navigation is awful
I think the RHIDE IDE had a better Info reader, IIRC, that one was useful.
I know of nothing in its history to suggest that it was a stop-gap system.
Stallman wanted to replace the Unix thing (which was always man pages) with INFO, and for many years deprecated man pages, which is part of why many GNU man pages are sub-par.
Not that non-GNU man pages are perfect, but still.
As for Unix being intended to be a stop-gap, your impression is simply historically incorrect, aside from philosophical issues like the claims made in the infamous Gabriel essay "Worse is Better".
> I could be wrong though, but almost every utility in UNIX screams this to me.
Unix/Linux is certainly not perfect, but this simply reflects the truth of Henry Spencer's aphorism, "Those who don't understand Unix are condemned to reinvent it, poorly."
People who think Unix got it all wrong, as opposed to merely having assorted warts, should read Raymond's "Art of Unix Programming".
I was more than a little startled that Raymond captured a lot of the truth of the subject; it's a good read, and can potentially make anyone a better programmer.
Edit: a more concise starting point: http://en.wikipedia.org/wiki/Unix_philosophy
Info really sucks compared to decent man pages. Sun did good man pages, go look at them, they are quite good.
A lot of the man page hate can be traced back to crappy gnu man pages that were just trying to get you to use info.
Info is cool, it's sort of like a web in text, but it isn't a replacement for a decently written concise man page.
minfo() { info "$1" --subnodes -o - 2> /dev/null | $PAGER; }They often have examples, though this is certainly better represented in some areas than others. One I looked at just the other day, man 7 aio, is 334 lines. Starts out describing the various aio functions and structures under DESCRIPTION, and has an EXAMPLE section: http://man7.org/linux/man-pages/man7/aio.7.html
Or, also off the top of my head, man 2 signalfd, 196 lines with example code: http://man7.org/linux/man-pages/man2/signalfd.2.html
Certainly there are man pages that aren't up to snuff (fix 'em!) but good man pages exist and many are great!
To the parent commenter, THANK YOU!
/signed someone who uses man pages all the time and seriously appreciates ones that have had thought put into them.
At least in my experience; maybe I've just been repeatedly unlucky.
I doubt it, though, because there isn't any standard template for them, unlike the situation for man pages, which have a number of standard sections that people generally copy.
Not doing trivial overflow checking before arithmetic.
Using results from said arithmetic as indices or starting points for memory writes.
My gut says that any application using this library to process untrusted input is exploitable.
Of course, it was never advertised as being secure :-)
Which is unusual, as many "better strings" libraries make claims about the security of the traditional way of string handling (and then go on to do it wrong).
Edit: To give a concrete example, someone here recommended BString just a few days ago. That library has a "security statement" in which it claims to prevent overflow type attacks by using signed integers instead of size_t, and then checking whether the result is negative after arithmetic. But signed overflow is undefined behavior in C, and these are not guaranteed to wrap around. And yes such things have been exploited. I don't know how likely or easy bstring is to "own" but in any case it's doinnit wrong.
And yes I checked the code, it does what it claims to do..
Whoa!
As if checking for overflow with unsigned is impossible...
Note that this could be fixed by using uint64_t type for example in the header, however in the original incarnation of SDS inside Redis this was not possible for memory usage concerns. In the standalone library I believe it is instead a great idea, since 2GB is no longer an acceptable limit.
From the point of view of the security the most concerning function looks like sdsrange(), there are sanity checks in place to avoid that indexes received from the outside can lead to issues, but I'll do a security review in the future in order to formally check for issues.
I am not dismissive of the problem. I just know from experience that the chances of code like that being fixed at all, let alone correctly, is near zero.
I often leave this out of my own code, but not without feeling somewhat guilty about it.
Consider what happens if an attacker controls initlen passed to sdsnewlen() (also via sdsnew() if he controls the initial string). He can overflow the arithmetic on malloc/calloc. Now either the memcpy or the NUL-byte assignment can write out of bounds, which is potentially exploitable, especially if the attacker has a nearly unbounded number of attempts (which they often do as far as online-facing services go) or if they can do things to affect the program's memory layout -- which they likely can if they're feeding the program with data.
But if these two aren't enough for a successful exploit (I wouldn't make any bets!), the initlen is assigned to sh->len (whoops, conversion to signed... and possible overflow), which then can completely throw off the arithmetic in sdsMakeRoomFor. Here I would actually be quite willing to bet an attacker can arrange these numbers so that he can do a more controlled out-of-bounds write in one of the functions that rely on sdsMakeRoomFor.
These two functions aren't necessarily the only problematic ones; they're just the ones I noticed after a minute of scanning.
And I don't really agree with what eliteraspberrie said; using the right types is a good start but it's absolutely not fine until after you actually do all the overflow checking; for an example of this, see the OpenBSD man page for malloc: http://www.openbsd.org/cgi-bin/man.cgi?query=malloc
Rule of thumb: whenever you do any arithmetic, ask yourself, can it overflow (how do you know it doesn't?) and who's in charge of making sure it doesn't? In this case it's your API's responsibility. This is exactly the same thing you do whenever you call a function: you need to know if it can fail, and if it can, you need to check the return value and do the right thing.
In the years I've spent reading CVEs (and the broken code that caused the alert), proof-of-concept exploits as well as real world exploits, I've learned that some incredibly subtle and seemingly insignificant things can be exploited. As a general rule, don't worry about exploitability, it's usually not worth your time to prove one way or another. Just fix the code whenever you see something that could go wrong. Make it easy for the next guy who audits your code; so he too can tell that your code is secure, just by seeing the right checks in the right place.
If you need a refresher on security issues in C code, I recommend Chapter 6 from The Art of Software Security Assment, made available free of charge by the authors: http://ptgmedia.pearsoncmg.com/images/0321444426/samplechapt...
It's a good read for anyone doing C these days, and covers a good deal of problems, including the one discussed here. It comes with snippets of known vulnerable real world code too.
Well, it is extracted from Redis so that should be easy to test
I haven't read much of the code at all, but a minor suggestion would be to change:
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
to struct sdshdr *sh = (void*)(s-sizeof *sh);
since the entire idea is that the pointer on the left-hand side is to the type whose size should be subtracted, I think it's better not to repeat the type but to "lock it" to the pointer instead. This also (of course) means we can drop the parenthesis with sizeof, since those are only needed when its argument is a type name.EDIT: Fixed here: https://github.com/antirez/sds/commit/c636fc6cd25e455a75dca2...
Seems everything is still working but I need definitely more unit tests... Note everything is covered right now.
#include <stddef.h>
int buf_offset = (int)offsetof(struct sdshdr, buf);
struct sdshdr *sh = s - buf_offset;
This is because the compiler might insert padding between your struct elements and the flexible array member. In your case, you're using only int types in the header, so padding shouldn't be an issue on most architectures, but consider the following: struct header {
int i;
char c;
char data[];
};
sizeof(struct header) on my machine is 8, but "data" starts at an offset of 5 from the beginning of the struct. So, to go from "data" pointer to the pointer representing the beginning of the struct, you will need to subtract 5, not 8. Here is a test program: #include <stdio.h>
#include <stddef.h>
struct header {
int i;
char c;
char data[];
};
int main(void)
{
struct header h = {0};
printf("offsetof %zu\n", offsetof(struct header, data));
printf("sizeof %zu\n", sizeof h);
printf("start %p, data start %p, delta %d\n",
(void *)&h, (void *)(h.data), (int)((char *)h.data - (char *)&h));
return 0;
}
http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n983.htm is relevant for this case as well.That surprised me. I thought( and read somewhere ) that the flexible array member comes right after the entire struct, which includes possible padding.
OP got away with it since he always allocates the size of the entire struct, which is 8, plus the string size. And since char doesn't have any alignment requirements it doesn't matter, because he always gets back to correct offset. So data member is basically not used, and if you check the code you will see that it actually in never used!
I think the main reason the OP got away with it is because in his structure, "sizeof(struct sdshdr)" is equal to the offset of "buf" in the struct. This is not necessarily true.
In particular, see the latest C standard draft (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), section 6.7.2.1 (Structure and union specifiers), paragraph 18 and 20-21. An example in the standard uses this code:
struct s { int n; double d[]; };
and says that "but it is possible that" sizeof (struct s) >= offsetof(struct s, d) + sizeof (double)Coincidentally I just read that specific paragraph earlier today for totally different reasons. :)
Will have to recheck some code.
My other favorite is casting the return value of malloc(), something you see a great deal of and that I always oppose. See http://stackoverflow.com/questions/605845/do-i-cast-the-resu... for my best arguments.
The (different) approaches taken by many of these libraries are interesting.
I'd add the str bits of http://libslack.org/ to that list.
Or alternatively, putting one "cleanup:" label at the end of the function and using "goto cleanup;" instead of break or return everywhere.
But goto is considered harmful, so that handy pattern seems unclean.
My question is: do you consider the "goto cleanup" pattern clean or not, and if not, what are better alternatives?
$ cd redis; grep goto *.c | wc -l 251
All the instances are like:
goto cleanup;
goto error;
goto badfmt;
goto numargserr;
In this context is easy to read and makes the code structure better. cleanup(state);
error(state);
etc.Just pull them each out into a function. You're still DRY and don't lose flow control.
In my honest opinion, there is never a need for goto.
thing1 = allocate_thing1();
if(!thing1) goto cleanup1;
thing2 = allocate_thing2();
if(!thing2) goto cleanup2;
...
cleanup3: deallocate_thing2(thing2);
cleanup2: deallocate_thing1(thing1);
cleanup1: return;Notice the cascade after the 'out' label; each label after it is for a different level of cleanup needed, depending on how deep into the system call the function encountered the error. Also notice that this is basically the kind of code one would generate for exceptions.
And I think it just highlights a lack of understanding about what might make goto "bad" in programming--IMHO it's "bad" when it makes control flow more complex, which undermines maintainability. Common "cleanup" idioms that only jump forward are not that bad, because they don't make control flow particularly more difficult to follow. (Whereas jumping backwards, especially across many state changes, can be very hard to follow.)
I don't see a time-space tradeoff here. Compiled naively, I expect the version with flags to be slower and take more space. Compiled sufficiently smartly, I expect them to be equivalent. I expect real compilers to be close enough to the latter for most but not all purposes, but I think if the flag version was more readable and maintainable the place to focus (collectively, medium term) would be on making compilers smart enough. Obviously, in the short term on specific projects you do what you need to.
"I think folks who argue that goto should never be used or has no legitimate uses, are just taking too much of a hard-line."
I agree, but that's because there are places where use of Go To makes things more readable and maintainable, not - primarily - because they are actually needed, per se, even with performance constraints (in the overwhelming majority of cases).
'And I think it just highlights a lack of understanding about what might make goto "bad" in programming--IMHO it's "bad" when it makes control flow more complex, which undermines maintainability.'
I agree wholeheartedly.
Stack allocated objects have their memory freed in C. Other resources are not released.
Consider the following C, with and without gcc extensions:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <fcntl.h>
#include <unistd.h>
#ifndef GCC_EXTENSIONS
#define GCC_EXTENSIONS 1
#endif
int main() {
{
#if GCC_EXTENSIONS
void close_fd(int *fdp) {
if(*fdp >= 0) close(*fdp);
}
#else
#define __attribute__(...)
#endif
int fd __attribute__((cleanup(close_fd))) = -1;
fd = open("somefile", O_CREAT | O_WRONLY, 0644);
system("ls /proc/self/fd");
}
system("ls /proc/self/fd");
}
with gcc extensions: 0 1 2 3 4
0 1 2 3
without gcc extensions: 0 1 2 3 4
0 1 2 3 4
[1] http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initial...Indeed, from the original editorial: "I remember having read the explicit recommendation to restrict the use of the go to statement to alarm exits, but I have not been able to trace it[.]"
It is the right way to handle exceptional conditions in modern C.
The hard part is that it takes a lot to be a good C++ programmer. C is such a smaller language, you can master a handful of best practices, gain experience, and become a competent C programmer. In contrast, C++ is such a large language you have to be active in learning everything — read Meyers, Modern C++, GoF's Design Patterns, etc. Anything short of this, and you're going to accidentally reinvent the wheel or do something stupid. But still, C++(11) is a terrific language that I'm learning to love.
Look at the Linux kernel; it's used extensively. For example: http://lxr.free-electrons.com/source/kernel/fork.c?v=3.3#L42...
It's a commonly used pattern, most famously in the Linux kernel.
#include <stdio.h>
typedef struct foo foo_t;
void foo_cleanup(foo_t *f);
struct foo { int i; };
void foo_cleanup(foo_t *f) {
printf("f %d\n", f->i);
}
int main() {
foo_t a __attribute__((cleanup(foo_cleanup))) = { 7 };
printf("l %d\n", __LINE__);
{
foo_t b __attribute__((cleanup(foo_cleanup))) = { 8 };
printf("l %d\n", __LINE__);
}
printf("l %d\n", __LINE__);
}
l 18
l 23
f 8
l 26
f 7GOTO _was_ considered harmful back in 1968(!) when structured programming was still in its infancy. Code of that time was usually peppered with GOTOs and therefore a pain to read.
Using GOTO for cleanup tasks is perfectly fine because it increases readability.
I, personally, think the pattern is clean. A labelled break is basically a goto anyway, yet not always available in your language. I never liked needing a flag to exit some nested structure early. I don't find reading that clean at all.
Also, a minor rant here: Dijkstra's "Go To Statement Considered Harmful" is often mentioned, sometime wordlessly, when goto is brought up, but it seems many of those people misunderstood what was actually being argued. Although he mentions being "convinced that the go to statement should be abolished from all 'higher level' programming languages," his main gripe was that the "unbridled use of the go to statement has an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress." He felt it was "as it stands is just too primitive; it is too much an invitation to make a mess of one's program." So, as others have pointed out already, his main issue was with goto being used in a way that resulted in unstructured, hard to understand programs.
Ah, if only C would guarantee tail call optimization >:)
Dijkstra never considered the kind of goto you describe as harmful. This is a distinction that's lost on most programmers now that the kind of languages that did have harmful gotos are dead and forgotten.
What he argued against was gotos that jump across procedure or scope boundaries.
http://www.u.arizona.edu/~rubinson/copyright_violations/Go_T...
According to Dijkstra, the really evil gotos are the ones that force you to keep a track of the whole execution path in order to figure out the current state of the program. Its much easier when you can look at a line of code and statically know what the program state will be like at that point.
A for loop is better than goto because you can look at a line inside it and instantly know that it will run a number of times, with the index changing in increasing order.
gotos for resource cleanup are good because you can look at a given line of code and know what resources still need to be freed.
Code that gets rid of gotos and break statements by blindly replacing them with tons of flags is just as bad as gotos because you still need to look at the whole execution path to figure out the state in the flags.
Same thing for the premature optimization quote from Knuth.
He doesn't explicitly say it, but it seems he agreed with that use of go to.
The solution I came up with myself is to have a destroy() function that is capable of cleaning up no matter what state the object is in.
Example: https://github.com/andrewrk/libgroove/blob/bc7d72589eb297342...
Notice all the calls to groove_playlist_destroy. And then in the destroy function it checks the conditions it has to before cleaning up.
I'm not saying it's necessarily the best answer but I think it's a pretty clean strategy.
On a related note - does anyone have recommendations for a similar small library for dealing with conversions from char * to wchar_t * and basic encoding duties? I'm working cross-platform, and so far I've stitched together some functions wrapping stuff like wcsrtombs() and WideCharToMultiByte().
Some time ago I forked the SDS code and added a set of additional utility functions around this for a project I was working on at the time (basic file reading, regex, LZF compression, Blowfish encrypt/decrypt, SHA256 etc).
This was from a fairly old SDS version so this looks like a good opportunity to sync up with the library version.
Repository is here: https://github.com/paulchakravarti/sdsutils
Then you can be sure that
sdscat(&s, "Some more data")
updates s to always point at the right memory address and you can't introduce hard to find bugs by forgetting to assign to s which the compiler wont warn you about.
If you'd pass just "s" instead of "&s" as the first parameter, the compiler would error out.So all functions modifying the string should take a pointer to it.
windows (for example) has had a comprehensive b-string library (type is called BSTR) for about 20 years - due to it's age and provenance it has the downside of thinking a character is a 16-bit value...
(The article is mildly interesting by itself)
// These provide test if assigned/not assigned
inline operator void*() const
{
return (void *)s;
}
inline bool operator!() const
{
if(s) return 0;
else return 1;
}I have the same gripe with the way the STL is designed. Too tedious to test for empty first before reading an item.
You also have x vector libraries for helping with bound checking in secure sensitive code and macro based generic data structures.
No thanks.
Ironically, in almost all of my uses of C++, I did end up writing or using a custom string library there too. (I was doing mostly console games or language interpreters.)
You say that, and yet every C++ project I've ever touched in my life has had its own string class with various levels of horror attached. My favorite was the one that stored everything internally as 32-bit characters to be Unicode safe, and was never used in a codebase that had to deal with Unicode.
Ruby, Java, Perl, PHP have all had security problems when interacting with C because they failed to properly distinguish binary-safe strings and C strings.
http://insecure.org/news/P55-07.txt http://cwe.mitre.org/data/definitions/626.html
s = sdscat(s,"Some more data");
Why not do this? sdscat(&s,"Some more data");
The latter would make the use-after-free error they're describing impossible. (Disadvantage #2, changing one reference but not others, would remain. And callers would still need to check for NULL if they intend to handle ENOMEM gracefully.)I assert there's no meaningful performance difference between the two.
And you still want to access the old value if reallocation failed.
Not nice I know.
Consistency is a tool that is often useful to promote program correctness. You're suggesting using it to accomplish the reverse.
If it's important aesthetically that all sds operations be consistent in this regard, you can structure the allocation interface in the same way:
sdserror sdsnew(sds*);
sds mystring = NULL;
sdsnew(&mystring, "Hello World!");
This is a common practice in relatively new interfaces like pthread_create.> And you still want to access the old value if reallocation failed.
If you want to provide commit-or-rollback semantics, you could signal error via return value rather than by replacing s with NULL:
if (sdscat(&s,"Some more data") != SDS_SUCCESS) {
/* failure path; s is unchanged */
} else {
/* s now has "Some more data in it" */
}
but this may be completely useless, depending on the environment(s) in which the library or program is intended to be used. On 64-bit Linux systems with memory overcommit enabled (the default), this failure path essentially shouldn't ever happen. Instead, some process (maybe yours, maybe not) will be picked by the kernel OOM killer. Many programs just use an allocate-or-die interface, as the sds README mentions. /* Like sdscatpritf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char *buf, *t;
size_t buflen = 16;
while(1) {
buf = malloc(buflen);
if (buf == NULL) return NULL;
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy); // <--- add this ----
if (buf[buflen-2] != '\0') {
free(buf);
buflen *= 2;
continue;
}
break;
}
t = sdscat(s, buf);
free(buf);
return t;
}
From the `man va_copy` on my system: Each invocation of va_copy() must be matched by
a corresponding invocation of va_end() in the same
function.
This is not likely to be a problem in most systems, but it can't hurt to be formally correct.Created a pull request for you: https://github.com/antirez/sds/pull/8
You could fix that by adding a "remote pointer" header field. Inside of sdscat, you would allocate a new sds struct, and set the remote pointer header field in `s` to the new sds structs location. You could also try to do a realloc and maybe youll get the same starting pointer back again.
Since with the sds library, you could potentially be getting back a new pointer for each operation, you could just as easily be working from an immutable string library. Do immutable strings have poor performance? I've never really considered it.
In no world is passing in a mutable value, having it mutate it, but then having to reassigning your variable superior.
Passing in an immutable value, and then assigning a fresh value is reasonable, but that's not what SDS is doing, AFAICT.
I think gcc has some decorations you can have to tell it to never ignore the return function.
I'm curious why s = sdscat(s,"Some more data"); didin't end up like: sdscat(&s,"Some more data");
It's basically a malloc()/free() implementation with printf, some formatting, and strcat bolted onto it. (Strictly speaking, it may or may not use free lists or whatever, but the use of the header and returned pointer is quite similar.)
And that's awesome.