Print this page
NEX-19025 CIFS gets confused with filenames containing enhanced Unicode
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
and: (fix build, check-rtime)
NEX-2460 libfksmbd should not link with libsmb
SMB-56 extended security NTLMSSP, inbound
@@ -20,11 +20,11 @@
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Msgbuf buffer management implementation. The smb_msgbuf interface is
* typically used to encode or decode SMB data using sprintf/scanf
@@ -51,10 +51,16 @@
static int buf_decode(smb_msgbuf_t *, char *, va_list ap);
static int buf_encode(smb_msgbuf_t *, char *, va_list ap);
static void *smb_msgbuf_malloc(smb_msgbuf_t *, size_t);
static int smb_msgbuf_chkerc(char *text, int erc);
+static int msgbuf_get_oem_string(smb_msgbuf_t *, char **, int);
+static int msgbuf_get_unicode_string(smb_msgbuf_t *, char **, int);
+static int msgbuf_put_oem_string(smb_msgbuf_t *, char *, int);
+static int msgbuf_put_unicode_string(smb_msgbuf_t *, char *, int);
+
+
/*
* Returns the offset or number of bytes used within the buffer.
*/
size_t
smb_msgbuf_used(smb_msgbuf_t *mb)
@@ -175,11 +181,11 @@
* smb_msgbuf_decode
*
* Decode a smb_msgbuf buffer as indicated by the format string into
* the variable arg list. This is similar to a scanf operation.
*
- * On success, returns the number of bytes encoded. Otherwise
+ * On success, returns the number of bytes decoded. Otherwise
* returns a -ve error code.
*/
int
smb_msgbuf_decode(smb_msgbuf_t *mb, char *fmt, ...)
{
@@ -211,19 +217,16 @@
* ensure correct behaviour and error handling.
*/
static int
buf_decode(smb_msgbuf_t *mb, char *fmt, va_list ap)
{
- uint32_t ival;
uint8_t c;
uint8_t *bvalp;
uint16_t *wvalp;
uint32_t *lvalp;
uint64_t *llvalp;
- char *cvalp;
char **cvalpp;
- smb_wchar_t wchar;
boolean_t repc_specified;
int repc;
int rc;
while ((c = *fmt++) != 0) {
@@ -322,80 +325,28 @@
case 'u': /* Convert from unicode if flags are set */
if (mb->flags & SMB_MSGBUF_UNICODE)
goto unicode_translation;
/*FALLTHROUGH*/
- case 's': /* get string */
- if (!repc_specified)
- repc = strlen((const char *)mb->scan) + 1;
- if (smb_msgbuf_has_space(mb, repc) == 0)
- return (SMB_MSGBUF_UNDERFLOW);
- if ((cvalp = smb_msgbuf_malloc(mb, repc * 2)) == 0)
- return (SMB_MSGBUF_UNDERFLOW);
+ case 's': /* get OEM string */
cvalpp = va_arg(ap, char **);
- *cvalpp = cvalp;
- /* Translate OEM to mbs */
- while (repc > 0) {
- wchar = *mb->scan++;
- repc--;
- if (wchar == 0)
+ if (!repc_specified)
+ repc = 0;
+ rc = msgbuf_get_oem_string(mb, cvalpp, repc);
+ if (rc != 0)
+ return (rc);
break;
- ival = smb_wctomb(cvalp, wchar);
- cvalp += ival;
- }
- *cvalp = '\0';
- if (repc > 0)
- mb->scan += repc;
- break;
- case 'U': /* get unicode string */
+ case 'U': /* get UTF-16 string */
unicode_translation:
- /*
- * Unicode strings are always word aligned.
- * The malloc'd area is larger than the
- * original string because the UTF-8 chars
- * may be longer than the wide-chars.
- */
- smb_msgbuf_word_align(mb);
- if (!repc_specified) {
- /*
- * Count bytes, including the null.
- */
- uint8_t *tmp_scan = mb->scan;
- repc = 2; /* the null */
- while ((wchar = LE_IN16(tmp_scan)) != 0) {
- tmp_scan += 2;
- repc += 2;
- }
- }
- if (smb_msgbuf_has_space(mb, repc) == 0)
- return (SMB_MSGBUF_UNDERFLOW);
- /*
- * Get space for translated string
- * Allocates worst-case size.
- */
- if ((cvalp = smb_msgbuf_malloc(mb, repc * 2)) == 0)
- return (SMB_MSGBUF_UNDERFLOW);
cvalpp = va_arg(ap, char **);
- *cvalpp = cvalp;
- /*
- * Translate unicode to mbs, stopping after
- * null or repc limit.
- */
- while (repc >= 2) {
- wchar = LE_IN16(mb->scan);
- mb->scan += 2;
- repc -= 2;
- if (wchar == 0)
+ if (!repc_specified)
+ repc = 0;
+ rc = msgbuf_get_unicode_string(mb, cvalpp, repc);
+ if (rc != 0)
+ return (rc);
break;
- ival = smb_wctomb(cvalp, wchar);
- cvalp += ival;
- }
- *cvalp = '\0';
- if (repc > 0)
- mb->scan += repc;
- break;
case 'M':
if (smb_msgbuf_has_space(mb, 4) == 0)
return (SMB_MSGBUF_UNDERFLOW);
@@ -414,12 +365,157 @@
}
return (SMB_MSGBUF_SUCCESS);
}
+/*
+ * msgbuf_get_oem_string
+ *
+ * Decode an OEM string, returning its UTF-8 form in strpp,
+ * allocated using smb_msgbuf_malloc (automatically freed).
+ * If max_bytes != 0, consume at most max_bytes of the mb.
+ * See also: mbc_marshal_get_oem_string
+ */
+static int
+msgbuf_get_oem_string(smb_msgbuf_t *mb, char **strpp, int max_bytes)
+{
+ char *mbs;
+ uint8_t *oembuf = NULL;
+ int oemlen; // len of OEM string, w/o null
+ int datalen; // OtW data len
+ int mbsmax; // max len of ret str
+ int rlen;
+ if (max_bytes == 0)
+ max_bytes = 0xffff;
+
+ /*
+ * Determine the OtW data length and OEM string length
+ * Note: oemlen is the string length (w/o null) and
+ * datalen is how much we move mb->scan
+ */
+ datalen = 0;
+ oemlen = 0;
+ for (;;) {
+ if (datalen >= max_bytes)
+ break;
+ /* in-line smb_msgbuf_has_space */
+ if ((mb->scan + datalen) >= mb->end)
+ return (SMB_MSGBUF_UNDERFLOW);
+ datalen++;
+ if (mb->scan[datalen - 1] == 0)
+ break;
+ oemlen++;
+ }
+
+ /*
+ * Get datalen bytes into a temp buffer
+ * sized with room to add a null.
+ * Free oembuf in smb_msgbuf_term
+ */
+ oembuf = smb_msgbuf_malloc(mb, datalen + 1);
+ if (oembuf == NULL)
+ return (SMB_MSGBUF_UNDERFLOW);
+ bcopy(mb->scan, oembuf, datalen);
+ mb->scan += datalen;
+ oembuf[oemlen] = '\0';
+
+ /*
+ * Get the buffer we'll return and convert to UTF-8.
+ * May take as much as double the space.
+ */
+ mbsmax = oemlen * 2;
+ mbs = smb_msgbuf_malloc(mb, mbsmax + 1);
+ if (mbs == NULL)
+ return (SMB_MSGBUF_UNDERFLOW);
+ rlen = smb_oemtombs(mbs, oembuf, mbsmax);
+ if (rlen < 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+ if (rlen > mbsmax)
+ rlen = mbsmax;
+ mbs[rlen] = '\0';
+ *strpp = mbs;
+ return (0);
+}
+
/*
+ * msgbuf_get_unicode_string
+ *
+ * Decode a UTF-16 string, returning its UTF-8 form in strpp,
+ * allocated using smb_msgbuf_malloc (automatically freed).
+ * If max_bytes != 0, consume at most max_bytes of the mb.
+ * See also: mbc_marshal_get_unicode_string
+ */
+static int
+msgbuf_get_unicode_string(smb_msgbuf_t *mb, char **strpp, int max_bytes)
+{
+ char *mbs;
+ uint16_t *wcsbuf = NULL;
+ int wcslen; // wchar count
+ int datalen; // OtW data len
+ size_t mbsmax; // max len of ret str
+ size_t rlen;
+
+ if (max_bytes == 0)
+ max_bytes = 0xffff;
+
+ /*
+ * Unicode strings are always word aligned.
+ */
+ smb_msgbuf_word_align(mb);
+
+ /*
+ * Determine the OtW data length and (WC) string length
+ * Note: wcslen counts 16-bit wide_chars (w/o null),
+ * and datalen is how much we move mb->scan
+ */
+ datalen = 0;
+ wcslen = 0;
+ for (;;) {
+ if (datalen >= max_bytes)
+ break;
+ /* in-line smb_msgbuf_has_space */
+ if ((mb->scan + datalen) >= mb->end)
+ return (SMB_MSGBUF_UNDERFLOW);
+ datalen += 2;
+ if (mb->scan[datalen - 2] == 0 &&
+ mb->scan[datalen - 1] == 0)
+ break;
+ wcslen++;
+ }
+
+ /*
+ * Get datalen bytes into a temp buffer
+ * sized with room to add a (WC) null.
+ * Note: wcsbuf has little-endian order
+ */
+ wcsbuf = smb_msgbuf_malloc(mb, datalen + 2);
+ if (wcsbuf == NULL)
+ return (SMB_MSGBUF_UNDERFLOW);
+ bcopy(mb->scan, wcsbuf, datalen);
+ mb->scan += datalen;
+ wcsbuf[wcslen] = 0;
+
+ /*
+ * Get the buffer we'll return and convert to UTF-8.
+ * May take as much 4X number of wide chars.
+ */
+ mbsmax = wcslen * MTS_MB_CUR_MAX;
+ mbs = smb_msgbuf_malloc(mb, mbsmax + 1);
+ if (mbs == NULL)
+ return (SMB_MSGBUF_UNDERFLOW);
+ rlen = smb_wcstombs(mbs, wcsbuf, mbsmax);
+ if (rlen == (size_t)-1)
+ return (SMB_MSGBUF_UNDERFLOW);
+ if (rlen > mbsmax)
+ rlen = mbsmax;
+ mbs[rlen] = '\0';
+ *strpp = mbs;
+ return (0);
+}
+
+/*
* smb_msgbuf_encode
*
* Encode a smb_msgbuf buffer as indicated by the format string using
* the variable arg list. This is similar to a sprintf operation.
*
@@ -464,12 +560,10 @@
uint32_t lval;
uint64_t llval;
uint8_t *bvalp;
char *cvalp;
uint8_t c;
- smb_wchar_t wchar;
- int count;
boolean_t repc_specified;
int repc;
int rc;
while ((c = *fmt++) != 0) {
@@ -569,86 +663,29 @@
case 'u': /* conditional unicode */
if (mb->flags & SMB_MSGBUF_UNICODE)
goto unicode_translation;
/* FALLTHROUGH */
- case 's': /* put string */
+ case 's': /* put OEM string */
cvalp = va_arg(ap, char *);
- if (!repc_specified) {
- repc = smb_sbequiv_strlen(cvalp);
- if (repc == -1)
- return (SMB_MSGBUF_OVERFLOW);
- if (!(mb->flags & SMB_MSGBUF_NOTERM))
- repc++;
- }
- if (smb_msgbuf_has_space(mb, repc) == 0)
- return (SMB_MSGBUF_OVERFLOW);
- while (repc > 0) {
- count = smb_mbtowc(&wchar, cvalp,
- MTS_MB_CHAR_MAX);
- if (count < 0)
- return (SMB_MSGBUF_DATA_ERROR);
- cvalp += count;
- if (wchar == 0)
+ if (!repc_specified)
+ repc = 0;
+ rc = msgbuf_put_oem_string(mb, cvalp, repc);
+ if (rc != 0)
+ return (rc);
break;
- *mb->scan++ = (uint8_t)wchar;
- repc--;
- if (wchar & 0xff00) {
- *mb->scan++ = wchar >> 8;
- repc--;
- }
- }
- if (*cvalp == '\0' && repc > 0 &&
- (mb->flags & SMB_MSGBUF_NOTERM) == 0) {
- *mb->scan++ = 0;
- repc--;
- }
- while (repc > 0) {
- *mb->scan++ = 0;
- repc--;
- }
- break;
- case 'U': /* put unicode string */
+ case 'U': /* put UTF-16 string */
unicode_translation:
- /*
- * Unicode strings are always word aligned.
- */
- smb_msgbuf_word_align(mb);
cvalp = va_arg(ap, char *);
- if (!repc_specified) {
- repc = smb_wcequiv_strlen(cvalp);
- if (!(mb->flags & SMB_MSGBUF_NOTERM))
- repc += 2;
- }
- if (!smb_msgbuf_has_space(mb, repc))
- return (SMB_MSGBUF_OVERFLOW);
- while (repc >= 2) {
- count = smb_mbtowc(&wchar, cvalp,
- MTS_MB_CHAR_MAX);
- if (count < 0)
- return (SMB_MSGBUF_DATA_ERROR);
- cvalp += count;
- if (wchar == 0)
+ if (!repc_specified)
+ repc = 0;
+ rc = msgbuf_put_unicode_string(mb, cvalp, repc);
+ if (rc != 0)
+ return (rc);
break;
- LE_OUT16(mb->scan, wchar);
- mb->scan += 2;
- repc -= 2;
- }
- if (*cvalp == '\0' && repc >= 2 &&
- (mb->flags & SMB_MSGBUF_NOTERM) == 0) {
- LE_OUT16(mb->scan, 0);
- mb->scan += 2;
- repc -= 2;
- }
- while (repc > 0) {
- *mb->scan++ = 0;
- repc--;
- }
- break;
-
case 'M':
if (smb_msgbuf_has_space(mb, 4) == 0)
return (SMB_MSGBUF_OVERFLOW);
*mb->scan++ = 0xFF;
@@ -663,12 +700,147 @@
}
return (SMB_MSGBUF_SUCCESS);
}
+/*
+ * Marshal a UTF-8 string (str) into mbc, converting to OEM codeset.
+ * Also write a null unless the repc count limits the length we put.
+ * When (repc > 0) the length we marshal must be exactly repc, and
+ * truncate or pad the mb data as necessary.
+ * See also: mbc_marshal_put_oem_string
+ */
+static int
+msgbuf_put_oem_string(smb_msgbuf_t *mb, char *mbs, int repc)
+{
+ uint8_t *oembuf = NULL;
+ uint8_t *s;
+ int oemlen;
+ int rlen;
+ /*
+ * Compute length of converted OEM string,
+ * NOT including null terminator
+ */
+ if ((oemlen = smb_sbequiv_strlen(mbs)) == -1)
+ return (SMB_MSGBUF_DATA_ERROR);
+
+ /*
+ * If repc not specified, put whole string + NULL,
+ * otherwise will truncate or pad as needed.
+ */
+ if (repc <= 0) {
+ repc = oemlen;
+ if ((mb->flags & SMB_MSGBUF_NOTERM) == 0)
+ repc += sizeof (char);
+ }
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ /*
+ * Convert into a temporary buffer
+ * Free oembuf in smb_msgbuf_term.
+ */
+ oembuf = smb_msgbuf_malloc(mb, oemlen + 1);
+ if (oembuf == NULL)
+ return (SMB_MSGBUF_UNDERFLOW);
+ rlen = smb_mbstooem(oembuf, mbs, oemlen);
+ if (rlen < 0)
+ return (SMB_MSGBUF_DATA_ERROR);
+ if (rlen > oemlen)
+ rlen = oemlen;
+ oembuf[rlen] = '\0';
+
+ /*
+ * Copy the converted string into the message,
+ * truncated or paded as required.
+ */
+ s = oembuf;
+ while (repc > 0) {
+ *mb->scan++ = *s;
+ if (*s != '\0')
+ s++;
+ repc--;
+ }
+
+ return (0);
+}
+
/*
+ * Marshal a UTF-8 string (str) into mbc, converting to UTF-16.
+ * Also write a null unless the repc count limits the length.
+ * When (repc > 0) the length we marshal must be exactly repc,
+ * and truncate or pad the mb data as necessary.
+ * See also: mbc_marshal_put_unicode_string
+ */
+static int
+msgbuf_put_unicode_string(smb_msgbuf_t *mb, char *mbs, int repc)
+{
+ smb_wchar_t *wcsbuf = NULL;
+ smb_wchar_t *wp;
+ size_t wcslen, wcsbytes;
+ size_t rlen;
+
+ /* align to word boundary */
+ smb_msgbuf_word_align(mb);
+
+ /*
+ * Compute length of converted UTF-16 string,
+ * NOT including null terminator (in bytes).
+ */
+ wcsbytes = smb_wcequiv_strlen(mbs);
+ if (wcsbytes == (size_t)-1)
+ return (SMB_MSGBUF_DATA_ERROR);
+
+ /*
+ * If repc not specified, put whole string + NULL,
+ * otherwise will truncate or pad as needed.
+ */
+ if (repc <= 0) {
+ repc = (int)wcsbytes;
+ if ((mb->flags & SMB_MSGBUF_NOTERM) == 0)
+ repc += sizeof (smb_wchar_t);
+ }
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ /*
+ * Convert into a temporary buffer
+ * Free wcsbuf in smb_msgbuf_term
+ */
+ wcslen = wcsbytes / 2;
+ wcsbuf = smb_msgbuf_malloc(mb, wcsbytes + 2);
+ if (wcsbuf == NULL)
+ return (SMB_MSGBUF_UNDERFLOW);
+ rlen = smb_mbstowcs(wcsbuf, mbs, wcslen);
+ if (rlen == (size_t)-1)
+ return (SMB_MSGBUF_DATA_ERROR);
+ if (rlen > wcslen)
+ rlen = wcslen;
+ wcsbuf[rlen] = 0;
+
+ /*
+ * Copy the converted string into the message,
+ * truncated or paded as required. Preserve
+ * little-endian order while copying.
+ */
+ wp = wcsbuf;
+ while (repc > 1) {
+ smb_wchar_t wchar = LE_IN16(wp);
+ LE_OUT16(mb->scan, wchar);
+ mb->scan += 2;
+ if (wchar != 0)
+ wp++;
+ repc -= sizeof (smb_wchar_t);
+ }
+ if (repc > 0)
+ *mb->scan++ = '\0';
+
+ return (0);
+}
+
+/*
* smb_msgbuf_malloc
*
* Allocate some memory for use with this smb_msgbuf. We increase the
* requested size to hold the list pointer and return a pointer
* to the area for use by the caller.