cmd: add sc_string_append #2745

Merged
merged 7 commits into from Feb 3, 2017

Conversation

Projects
None yet
3 participants
Contributor

zyga commented Jan 30, 2017

This patch adds a security-paranoid version of strcat that is more
sensitive to buffer overflows and other misuse of common string
utilities.

Signed-off-by: Zygmunt Krynicki zygmunt.krynicki@canonical.com

zyga added some commits Jan 30, 2017

cmd: add sc_must_stpcpy
This patch adds a security-paranoid version of stpcpy that is more
sensitive to buffer overflows and other misuse of common string
utilities.

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd: tweak output to be identical on 32 and 64bit
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd: use signed char (fix ppc64el)
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

LGTM!

zyga added some commits Jan 31, 2017

cmd: remove useless check
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd: move variable declaration closer to use
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
@@ -69,3 +70,48 @@ int sc_must_snprintf(char *str, size_t size, const char *format, ...)
return n;
}
+
+char *sc_must_stpcpy(char *buf, size_t buf_size, char *dest, const char *src)
@jdstrand

jdstrand Jan 31, 2017

Contributor

I think this function is misnamed since the arg list doesn't match stpcpy() (note that sc_must_snprintf() matches snprintf()). With something named 'sc_must_stpcpy' I would expect it to be 'sc_must_stpcpy(char *dest, const char *src)`. Of course, it cannot be that cause you need to know the buf_size to make sure everything is ok.

@zyga

zyga Jan 31, 2017

Contributor

Yeah, I wanted to keep the spirit of stpcpy which is fast and easy way to append while adding the safety.

+ }
+ // Sanity check, the code doesn't need buffers larger than a few KBs so
+ // prevent corrupted or otherwise huge buffers from seeming "valid".
+ if (buf_size >= 0xFFFF) {
@jdstrand

jdstrand Jan 31, 2017

Contributor

What is this meant to protect against? The point of the function is to make sure that 'src + dst + \0' fits in buf_size. Why do we care how big buf_size is?

@zyga

zyga Jan 31, 2017

Contributor

Because we don't need big buffers and our protections break down (note that overflow detection is not implemented to guard against integer overflow). Limiting the maximum buffer size to something small (and entirely reasonable for where we want to use this) adds some security.

+ &dest[src_len] - &buf[buf_size] + 1);
+ }
+ memcpy(dest, src, src_len + 1);
+ return &dest[src_len + 1];
@jdstrand

jdstrand Jan 31, 2017

Contributor

I provided an example for sc_must_strncat() (#2658 (comment)) that both has the same arguments as strncat() and is implemented more simply and you don't have to worry about the stpcpy semantics. Why did you opt for this more complicated implementation? If you insist on stpcpy semantics, you could start with the sc_must_strncat() (renaming it to something appropriate) and just 'return &dst[strlen(dst)];', but strncat() seems the more obviously useful choice for consumers of this function.

@zyga

zyga Jan 31, 2017

Contributor

I'm not sure if you meant this but strncat is not protecting anything in the output buffer. strncat just limits the amount of data read from the source buffer.

The stpcpy semantics is nice because it allows for fast and simple appending. I wanted to keep that while making it safe.

Edit: to clarify my point. With strncat you need to keep track of used space and limit n all the time. This makes it error prone and, I think, defeats the purpose for security-sensitive code. It is good for appending a substring but not for appending while not overflowing.

@jdstrand

jdstrand Feb 1, 2017

Contributor

Meh, in the original comment in the other PR I had called this sc_append_string(char *dst, char *src, size_t dst_size) since it safely considers the dst size when appending src to dst, then at the last minute changed it without thinking critically about that (sorry for the confusion). I'll now refer to it as sc_string_append(char *dst, char *src, size_t dst_size).

I still maintain that sc_append_string(char *dst, const char *src, size_t dst_size) is a simpler implementation and easy to use (just keep appending src and dst always points to the whole string) whereas stpcpy is more specialized and focuses on the implementation detail of how the string is built up.

Re strncat() safety> strncat() can be used safely if you are checking the the sizes correctly but a must_strncat() can't be written without changing the function's signature for the reasons outlined.

@zyga

zyga Feb 1, 2017

Contributor

I agree about how stpcpy is an optimization detail for how append should work. If you want I can rename this to sc_append_string and just use that everywhere.

@zyga

zyga Feb 1, 2017

Contributor

OR: sc_must_append_string() or similar.

cmd: replace sc_must_stpcpy with sc_append_string
This patch simplified the string append code to be more like safe strcat
than stpcpy (stpcpy is just an optimization that we don't really need in
security sensitive code that is not performance critical).

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

@zyga zyga changed the title from cmd: add sc_must_stpcpy to cmd: add sc_string_append Feb 2, 2017

If you change to use exactly what I recommended, feel free to commit after making the changes, otherwise please have Tyler or Seth review in my absence.

@@ -69,3 +70,29 @@ int sc_must_snprintf(char *str, size_t size, const char *format, ...)
return n;
}
+
+void sc_string_append(char *buf, size_t buf_size, const char *str)
@jdstrand

jdstrand Feb 2, 2017

Contributor

Can you rename these as:

size_t sc_string_append(char *dst, size_t dst_size, const char *str)

This will make things more readable IMHO and lets the caller know the new size of dst (previously buf).

@zyga

zyga Feb 3, 2017

Contributor

Done

+ }
+ if (str == NULL) {
+ die("cannot append string: string is NULL");
+ }
@jdstrand

jdstrand Feb 2, 2017

Contributor

Above here is fine.

@zyga

zyga Feb 3, 2017

Contributor

I feel so silly not being able to write a correct strcat after so many tries. Thank you for guiding me.

+ buf_len + str_len + 1 - buf_size);
+ }
+ memcpy(buf + buf_len, str, str_len + 1);
+}
@jdstrand

jdstrand Feb 2, 2017

Contributor

Please rewrite the portion of the function after my comment 'Above here is fine' (I started to do inline comments but it will be easier this way) as:

	size_t dst_len = strnlen(dst, dst_size);
	if (dst_len == dst_size) {
		die("cannot append string: dst is unterminated");
	}

	size_t max_str_len = dst_size - dst_len;
	size_t str_len = strnlen(str, max_str_len);
	if (str_len == max_str_len) {
		die("cannot append string: str is too long or unterminated");
	}

	// Append the string
	memcpy(dst + dst_len, str, str_len);

	// Ensure we are terminated
	dst[dst_len + str_len] = '\0';

	// return the new size
	return strlen(dst);
@zyga

zyga Feb 2, 2017

Contributor

Curious, what use is the new size? If this wasn't a function that dies on error I'd understand but honestly I cannot see us using the return value.

@jdstrand

jdstrand Feb 2, 2017

Contributor

It isn't for safety, it is for convenience so the caller doesn't have to calculate it (this is a library function after all).

@zyga

zyga Feb 3, 2017

Contributor

Done. I never thought about using strnlen and in retrospective it is much better and safer to do it this way.

+ *
+ * The string cannot overlap the buffer in any way.
+ **/
+void sc_string_append(char *buf, size_t buf_size, const char *str);
@jdstrand

jdstrand Feb 2, 2017

Contributor

Please use: size_t sc_string_append(char *dst, size_t dst_size, const char *str);

@zyga

zyga Feb 3, 2017

Contributor

Done

cmd: rewrite sc_string_append based on jdstrand's suggestion
This version is much safer (thanks!) because is uses strnlen that
ensures we don't scan past the destination buffer. In addition it
doesn't scan the source buffer without bounds and uses the remaining
space in the destination buffer as the limit.

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

@zyga zyga merged commit 2b03233 into snapcore:master Feb 3, 2017

6 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
xenial-amd64 autopkgtest finished (success)
Details
xenial-i386 autopkgtest finished (success)
Details
xenial-ppc64el autopkgtest finished (success)
Details
yakkety-amd64 autopkgtest finished (success)
Details
zesty-amd64 autopkgtest finished (success)
Details

@zyga zyga deleted the zyga:sc_must_stpcpy branch Feb 3, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment