239 lines
7.3 KiB
Diff
239 lines
7.3 KiB
Diff
From 2ba1dea5493fb2f5a5be2dd263ce46ccb5f8ec76 Mon Sep 17 00:00:00 2001
|
|
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
Date: Tue, 22 Oct 2019 16:08:24 +0200
|
|
Subject: CVE-2019-14889: misc: Add function to quote file names
|
|
|
|
The added function quote file names strings to be used in a shell.
|
|
Special cases are treated for the charactes '\'' and '!'.
|
|
|
|
Fixes T181
|
|
|
|
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
|
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
|
|
(cherry picked from commit c4ad1aba9860e02fe03ef3f58a047964e9e765fc)
|
|
---
|
|
include/libssh/misc.h | 8 +++
|
|
src/misc.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
2 files changed, 192 insertions(+)
|
|
|
|
diff --git a/include/libssh/misc.h b/include/libssh/misc.h
|
|
index bc50cff8..0531d4f3 100644
|
|
--- a/include/libssh/misc.h
|
|
+++ b/include/libssh/misc.h
|
|
@@ -50,6 +50,12 @@ struct ssh_timestamp {
|
|
long useconds;
|
|
};
|
|
|
|
+enum ssh_quote_state_e {
|
|
+ NO_QUOTE,
|
|
+ SINGLE_QUOTE,
|
|
+ DOUBLE_QUOTE
|
|
+};
|
|
+
|
|
struct ssh_list *ssh_list_new(void);
|
|
void ssh_list_free(struct ssh_list *list);
|
|
struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list);
|
|
@@ -81,4 +87,6 @@ int ssh_timeout_update(struct ssh_timestamp *ts, int timeout);
|
|
|
|
int ssh_match_group(const char *group, const char *object);
|
|
|
|
+int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len);
|
|
+
|
|
#endif /* MISC_H_ */
|
|
diff --git a/src/misc.c b/src/misc.c
|
|
index 18c9745e..b042b46d 100644
|
|
--- a/src/misc.c
|
|
+++ b/src/misc.c
|
|
@@ -1108,4 +1108,188 @@ char *strndup(const char *s, size_t n)
|
|
}
|
|
#endif /* ! HAVE_STRNDUP */
|
|
|
|
+/**
|
|
+ * @internal
|
|
+ *
|
|
+ * @brief Quote file name to be used on shell.
|
|
+ *
|
|
+ * Try to put the given file name between single quotes. There are special
|
|
+ * cases:
|
|
+ *
|
|
+ * - When the '\'' char is found in the file name, it is double quoted
|
|
+ * - example:
|
|
+ * input: a'b
|
|
+ * output: 'a'"'"'b'
|
|
+ * - When the '!' char is found in the file name, it is replaced by an unquoted
|
|
+ * verbatim char "\!"
|
|
+ * - example:
|
|
+ * input: a!b
|
|
+ * output 'a'\!'b'
|
|
+ *
|
|
+ * @param[in] file_name File name string to be quoted before used on shell
|
|
+ * @param[out] buf Buffer to receive the final quoted file name. Must
|
|
+ * have room for the final quoted string. The maximum
|
|
+ * output length would be (3 * strlen(file_name) + 1)
|
|
+ * since in the worst case each character would be
|
|
+ * replaced by 3 characters, plus the terminating '\0'.
|
|
+ * @param[in] buf_len The size of the provided output buffer
|
|
+ *
|
|
+ * @returns SSH_ERROR on error; length of the resulting string not counting the
|
|
+ * string terminator '\0'
|
|
+ * */
|
|
+int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len)
|
|
+{
|
|
+ const char *src = NULL;
|
|
+ char *dst = NULL;
|
|
+ size_t required_buf_len;
|
|
+
|
|
+ enum ssh_quote_state_e state = NO_QUOTE;
|
|
+
|
|
+ if (file_name == NULL || buf == NULL || buf_len == 0) {
|
|
+ SSH_LOG(SSH_LOG_WARNING, "Invalid parameter");
|
|
+ return SSH_ERROR;
|
|
+ }
|
|
+
|
|
+ /* Only allow file names smaller than 32kb. */
|
|
+ if (strlen(file_name) > 32 * 1024) {
|
|
+ SSH_LOG(SSH_LOG_WARNING, "File name too long");
|
|
+ return SSH_ERROR;
|
|
+ }
|
|
+
|
|
+ /* Paranoia check */
|
|
+ required_buf_len = (size_t)3 * strlen(file_name) + 1;
|
|
+ if (required_buf_len > buf_len) {
|
|
+ SSH_LOG(SSH_LOG_WARNING, "Buffer too small");
|
|
+ return SSH_ERROR;
|
|
+ }
|
|
+
|
|
+ src = file_name;
|
|
+ dst = buf;
|
|
+
|
|
+ while ((*src != '\0')) {
|
|
+ switch (*src) {
|
|
+
|
|
+ /* The '\'' char is double quoted */
|
|
+
|
|
+ case '\'':
|
|
+ switch (state) {
|
|
+ case NO_QUOTE:
|
|
+ /* Start a new double quoted string. The '\'' char will be
|
|
+ * copied to the beginning of it at the end of the loop. */
|
|
+ *dst++ = '"';
|
|
+ break;
|
|
+ case SINGLE_QUOTE:
|
|
+ /* Close the current single quoted string and start a new double
|
|
+ * quoted string. The '\'' char will be copied to the beginning
|
|
+ * of it at the end of the loop. */
|
|
+ *dst++ = '\'';
|
|
+ *dst++ = '"';
|
|
+ break;
|
|
+ case DOUBLE_QUOTE:
|
|
+ /* If already in the double quoted string, keep copying the
|
|
+ * sequence of chars. */
|
|
+ break;
|
|
+ default:
|
|
+ /* Should never be reached */
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /* When the '\'' char is found, the resulting state will be
|
|
+ * DOUBLE_QUOTE in any case*/
|
|
+ state = DOUBLE_QUOTE;
|
|
+ break;
|
|
+
|
|
+ /* The '!' char is replaced by unquoted "\!" */
|
|
+
|
|
+ case '!':
|
|
+ switch (state) {
|
|
+ case NO_QUOTE:
|
|
+ /* The '!' char is interpreted in some shells (e.g. CSH) even
|
|
+ * when is quoted with single quotes. Replace it with unquoted
|
|
+ * "\!" which is correctly interpreted as the '!' character. */
|
|
+ *dst++ = '\\';
|
|
+ break;
|
|
+ case SINGLE_QUOTE:
|
|
+ /* Close the current quoted string and replace '!' for unquoted
|
|
+ * "\!" */
|
|
+ *dst++ = '\'';
|
|
+ *dst++ = '\\';
|
|
+ break;
|
|
+ case DOUBLE_QUOTE:
|
|
+ /* Close current quoted string and replace "!" for unquoted
|
|
+ * "\!" */
|
|
+ *dst++ = '"';
|
|
+ *dst++ = '\\';
|
|
+ break;
|
|
+ default:
|
|
+ /* Should never be reached */
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /* When the '!' char is found, the resulting state will be NO_QUOTE
|
|
+ * in any case*/
|
|
+ state = NO_QUOTE;
|
|
+ break;
|
|
+
|
|
+ /* Ordinary chars are single quoted */
|
|
+
|
|
+ default:
|
|
+ switch (state) {
|
|
+ case NO_QUOTE:
|
|
+ /* Start a new single quoted string */
|
|
+ *dst++ = '\'';
|
|
+ break;
|
|
+ case SINGLE_QUOTE:
|
|
+ /* If already in the single quoted string, keep copying the
|
|
+ * sequence of chars. */
|
|
+ break;
|
|
+ case DOUBLE_QUOTE:
|
|
+ /* Close current double quoted string and start a new single
|
|
+ * quoted string. */
|
|
+ *dst++ = '"';
|
|
+ *dst++ = '\'';
|
|
+ break;
|
|
+ default:
|
|
+ /* Should never be reached */
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /* When an ordinary char is found, the resulting state will be
|
|
+ * SINGLE_QUOTE in any case*/
|
|
+ state = SINGLE_QUOTE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Copy the current char to output */
|
|
+ *dst++ = *src++;
|
|
+ }
|
|
+
|
|
+ /* Close the quoted string when necessary */
|
|
+
|
|
+ switch (state) {
|
|
+ case NO_QUOTE:
|
|
+ /* No open string */
|
|
+ break;
|
|
+ case SINGLE_QUOTE:
|
|
+ /* Close current single quoted string */
|
|
+ *dst++ = '\'';
|
|
+ break;
|
|
+ case DOUBLE_QUOTE:
|
|
+ /* Close current double quoted string */
|
|
+ *dst++ = '"';
|
|
+ break;
|
|
+ default:
|
|
+ /* Should never be reached */
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /* Put the string terminator */
|
|
+ *dst = '\0';
|
|
+
|
|
+ return dst - buf;
|
|
+
|
|
+error:
|
|
+ return SSH_ERROR;
|
|
+}
|
|
+
|
|
/** @} */
|
|
--
|
|
cgit v1.2.1
|
|
|