/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2013-2021 Tintri by DDN, Inc. All rights reserved. */ /* * SMB mbuf marshaling encode/decode. */ #include #define MALLOC_QUANTUM 80 #define DECODE_NO_ERROR 0 #define DECODE_NO_MORE_DATA 1 #define DECODE_ALLOCATION_ERROR 2 #define DECODE_CONVERSION_ERROR 3 static int mbc_marshal_make_room(mbuf_chain_t *, int32_t); static void mbc_marshal_store_byte(mbuf_chain_t *, uint8_t); static int mbc_marshal_put_char(mbuf_chain_t *mbc, uint8_t); static int mbc_marshal_put_short(mbuf_chain_t *mbc, uint16_t); static int mbc_marshal_put_long(mbuf_chain_t *mbc, uint32_t); static int mbc_marshal_put_long_long(mbuf_chain_t *mbc, uint64_t); static int mbc_marshal_put_oem_string(mbuf_chain_t *, char *, int); static int mbc_marshal_put_unicode_string(mbuf_chain_t *, char *, int); static int mbc_marshal_put_uio(mbuf_chain_t *, struct uio *); static int mbc_marshal_put_mbufs(mbuf_chain_t *mbc, mbuf_t *m); static int mbc_marshal_put_mbuf_chain(mbuf_chain_t *mbc, mbuf_chain_t *nmbc); static uint8_t mbc_marshal_fetch_byte(mbuf_chain_t *mbc); static int mbc_marshal_get_char(mbuf_chain_t *mbc, uint8_t *data); static int mbc_marshal_get_short(mbuf_chain_t *mbc, uint16_t *data); static int mbc_marshal_get_long(mbuf_chain_t *mbc, uint32_t *data); static uint64_t qswap(uint64_t ll); static int mbc_marshal_get_odd_long_long(mbuf_chain_t *mbc, uint64_t *data); static int mbc_marshal_get_long_long(mbuf_chain_t *mbc, uint64_t *data); static int mbc_marshal_get_oem_string(smb_request_t *, mbuf_chain_t *, char **, int); static int mbc_marshal_get_unicode_string(smb_request_t *, mbuf_chain_t *, char **, int); static int mbc_marshal_get_mbufs(mbuf_chain_t *, int32_t, mbuf_t **); static int mbc_marshal_get_mbuf_chain(mbuf_chain_t *, int32_t, mbuf_chain_t *); static int mbc_marshal_get_uio(mbuf_chain_t *, struct uio *); static int mbc_marshal_get_skip(mbuf_chain_t *, uint_t); /* * smb_mbc_vdecodef * * This function reads the contents of the mbc chain passed in under the list * of arguments passed in. * * The format string provides a description of the parameters passed in as well * as an action to be taken by smb_mbc_vdecodef(). * * % Pointer to an SMB request structure (smb_request_t *). There * should be only one of these in the string. * * C Pointer to an mbuf chain. Copy to that mbuf chain the number of * bytes specified (number preceding C). * * m Pointer to an mbuf. Copy to that mbuf the number of bytes * specified (number preceding m). * * M Read the 32 bit value at the current location of the mbuf chain * and check if it matches the signature of an SMB1 request (SMBx). * * N Read the 32 bit value at the current location of the mbuf chain * and check if it matches the signature of an SMB2 request (SMBx). * * b Pointer to a buffer. Copy to that buffer the number of bytes * specified (number preceding b). * * c Same as 'b'. * * w Pointer to a word (16bit value). Copy the next 16bit value into * that location. * * l Pointer to a long (32bit value). Copy the next 32bit value into * that location. * * q Pointer to a quad (64bit value). Copy the next 64bit value into * that location. * * Q Same as above with a call to qswap(). * * B Pointer to a vardata_block structure. That structure is used to * retrieve data from the mbuf chain (an iovec type structure is * embedded in a vardata_block). * * D Pointer to a vardata_block structure. That structure is used to * retrieve data from the mbuf chain, however, two fields of the * vardata_block structure (tag and len) are first initialized * using the mbuf chain itself. * * V Same as 'D'. * * L * * A * * P Same as 'A' * * S Same as 'A' * * u Pointer to a string pointer. Allocate memory and retrieve the * string at the current location in the mbuf chain. Store the * address to the buffer allocated at the address specified by * the pointer. In addition if an sr was passed and it indicates * that the string is an unicode string, convert it. * * s Same as 'u' without convertion. * * U Same as 'u'. The string to retrieve is unicode. * * y Pointer to a 32bit value. Read the dos time at the current mbuf * chain location, convert it to unix time and store it at the * location indicated by the pointer. * * Y Same as 'y' bt the dos time coded in the mbuf chain is inverted. * * . Skip the number of bytes indicated by the number preceding '.'. * * , Same as '.' but take in account it is an unicode string. */ int smb_mbc_vdecodef(mbuf_chain_t *mbc, const char *fmt, va_list ap) { uint8_t c; uint8_t cval; uint8_t *cvalp; char **charpp; uint16_t wval; uint16_t *wvalp; uint32_t *lvalp; uint64_t *llvalp; smb_vdb_t *vdp; smb_request_t *sr = NULL; uint32_t lval; int unicode = 0; int repc; boolean_t repc_specified; while ((c = *fmt++) != 0) { repc_specified = B_FALSE; repc = 1; if ('0' <= c && c <= '9') { repc = 0; do { repc = repc * 10 + c - '0'; c = *fmt++; } while ('0' <= c && c <= '9'); repc_specified = B_TRUE; } else if (c == '#') { repc = va_arg(ap, int); c = *fmt++; repc_specified = B_TRUE; } switch (c) { case '%': sr = va_arg(ap, struct smb_request *); if (sr->session->dialect >= SMB_VERS_2_BASE) { unicode = 1; break; } unicode = sr->smb_flg2 & SMB_FLAGS2_UNICODE; break; case 'C': /* Mbuf_chain */ if (mbc_marshal_get_mbuf_chain(mbc, repc, va_arg(ap, mbuf_chain_t *)) != 0) return (-1); break; case 'm': /* struct_mbuf */ if (mbc_marshal_get_mbufs(mbc, repc, va_arg(ap, mbuf_t **)) != 0) return (-1); break; case 'M': if (mbc_marshal_get_long(mbc, &lval) != 0) return (-1); if (lval != 0x424D53FF) /* 0xFF S M B */ return (-1); break; case 'N': if (mbc_marshal_get_long(mbc, &lval) != 0) return (-1); if (lval != 0x424D53FE) /* 0xFE S M B */ return (-1); break; case 'b': case 'c': cvalp = va_arg(ap, uint8_t *); if (MBC_ROOM_FOR(mbc, repc) == 0) /* Data will never be available */ return (-1); while (repc-- > 0) *cvalp++ = mbc_marshal_fetch_byte(mbc); break; case 'w': wvalp = va_arg(ap, uint16_t *); while (repc-- > 0) if (mbc_marshal_get_short(mbc, wvalp++) != 0) return (-1); break; case 'l': lvalp = va_arg(ap, uint32_t *); while (repc-- > 0) if (mbc_marshal_get_long(mbc, lvalp++) != 0) return (-1); break; case 'q': llvalp = va_arg(ap, uint64_t *); while (repc-- > 0) if (mbc_marshal_get_long_long( mbc, llvalp++) != 0) return (-1); break; case 'Q': llvalp = va_arg(ap, uint64_t *); while (repc-- > 0) if (mbc_marshal_get_odd_long_long( mbc, llvalp++) != 0) return (-1); break; case 'B': vdp = va_arg(ap, struct vardata_block *); vdp->vdb_tag = 0; vdp->vdb_len = repc; vdp->vdb_uio.uio_iov = &vdp->vdb_iovec[0]; vdp->vdb_uio.uio_iovcnt = MAX_IOVEC; vdp->vdb_uio.uio_extflg = UIO_COPY_DEFAULT; vdp->vdb_uio.uio_resid = repc; if (mbc_marshal_get_uio(mbc, &vdp->vdb_uio) != 0) return (-1); break; case 'D': case 'V': vdp = va_arg(ap, struct vardata_block *); if (mbc_marshal_get_char(mbc, &vdp->vdb_tag) != 0) return (-1); if (mbc_marshal_get_short(mbc, &wval) != 0) return (-1); vdp->vdb_len = (uint32_t)wval; vdp->vdb_uio.uio_iov = &vdp->vdb_iovec[0]; vdp->vdb_uio.uio_iovcnt = MAX_IOVEC; vdp->vdb_uio.uio_extflg = UIO_COPY_DEFAULT; vdp->vdb_uio.uio_resid = vdp->vdb_len; if (vdp->vdb_len != 0) { if (mbc_marshal_get_uio(mbc, &vdp->vdb_uio) != 0) return (-1); } break; case 'L': if (mbc_marshal_get_char(mbc, &cval) != 0) return (-1); if (cval != 2) return (-1); goto oem_conversion; case 'A': case 'S': if (mbc_marshal_get_char(mbc, &cval) != 0) return (-1); if (((c == 'A' || c == 'S') && cval != 4) || (c == 'L' && cval != 2)) return (-1); /* FALLTHROUGH */ case 'u': /* Convert from unicode if flags are set */ if (unicode) goto unicode_translation; /* FALLTHROUGH */ case 's': /* get OEM string */ oem_conversion: ASSERT(sr != NULL); charpp = va_arg(ap, char **); if (!repc_specified) repc = 0; if (mbc_marshal_get_oem_string(sr, mbc, charpp, repc) != 0) return (-1); break; case 'U': /* get UTF-16 string */ unicode_translation: ASSERT(sr != 0); charpp = va_arg(ap, char **); if (!repc_specified) repc = 0; if (mbc_marshal_get_unicode_string(sr, mbc, charpp, repc) != 0) return (-1); break; case 'Y': /* dos time to unix time tt/dd */ lvalp = va_arg(ap, uint32_t *); while (repc-- > 0) { short d, t; if (mbc_marshal_get_short(mbc, (uint16_t *)&t) != 0) return (-1); if (mbc_marshal_get_short(mbc, (uint16_t *)&d) != 0) return (-1); *lvalp++ = smb_time_dos_to_unix(d, t); } break; case 'y': /* dos time to unix time dd/tt */ lvalp = va_arg(ap, uint32_t *); while (repc-- > 0) { short d, t; if (mbc_marshal_get_short(mbc, (uint16_t *)&d) != 0) return (-1); if (mbc_marshal_get_short(mbc, (uint16_t *)&t) != 0) return (-1); *lvalp++ = smb_time_dos_to_unix(d, t); } break; case ',': if (unicode) repc *= 2; /* FALLTHROUGH */ case '.': if (mbc_marshal_get_skip(mbc, repc) != 0) return (-1); break; default: ASSERT(0); return (-1); } } return (0); } /* * smb_mbc_decodef * * This function reads the contents of the mbc chain passed in under the * control of the format fmt. * * (for a description of the format string see smb_mbc_vencodef()). */ int smb_mbc_decodef(mbuf_chain_t *mbc, const char *fmt, ...) { int xx; va_list ap; va_start(ap, fmt); xx = smb_mbc_vdecodef(mbc, fmt, ap); va_end(ap); return (xx); } /* * smb_mbc_peek * * This function reads the contents of the mbc passed in at the specified offset * under the control of the format fmt. The offset of the chain passed in is not * modified. * * (for a description of the format string see smb_mbc_vdecodef()). */ int smb_mbc_peek(mbuf_chain_t *mbc, int offset, const char *fmt, ...) { mbuf_chain_t tmp; va_list ap; int xx; va_start(ap, fmt); (void) MBC_SHADOW_CHAIN(&tmp, mbc, offset, mbc->max_bytes - offset); xx = smb_mbc_vdecodef(&tmp, fmt, ap); va_end(ap); return (xx); } /* * smb_mbc_vencodef * * This function builds a stream of bytes in the mbc chain passed in under the * control of the list of arguments passed in. * * The format string provides a description of the parameters passed in as well * as an action to be taken by smb_mbc_vencodef(). * * \b Restore the mbuf chain offset to its initial value. * * % Pointer to an SMB request structure (smb_request_t *). There * should be only one of these in the string. If an sr in present * it will be used to determine if unicode conversion should be * applied to the strings. * * C Pointer to an mbuf chain. Copy that mbuf chain into the * destination mbuf chain. * * D Pointer to a vardata_block structure. Copy the data described * by that structure into the mbuf chain. The tag field is hard * coded to '1'. * * M Write the SMB1 request signature ('SMBX') into the mbuf chain. * * N Write the SMB2 request signature ('SMBX') into the mbuf chain. * * T Pointer to a timestruc_t. Convert the content of the structure * into NT time and store the result of the conversion in the * mbuf chain. * * V Same as 'D' but the tag field is hard coded to '5'. * * b Byte. Store the byte or the nymber of bytes specified into the * the mbuf chain. A format string like this "2b" would require 2 * bytes to be passed in. * * m Pointer to an mbuf. Copy the contents of the mbuf into the mbuf * chain. * * c Pointer to a buffer. Copy the buffer into the mbuf chain. The * size of the buffer is indicated by the number preceding 'c'. * * w Word (16bit value). Store the word or the number of words * specified into the the mbuf chain. A format string like this * "2w" would require 2 words to be passed in. * * l Long (32bit value). Store the long or the number of longs * specified into the the mbuf chain. A format string like this * "2l" would require 2 longs to be passed in. * * q Quad (64bit value). Store the quad or the number of quads * specified into the the mbuf chain. A format string like this * "2q" would require 2 quads to be passed in. * * L Pointer to a string. Store the string passed in into the mbuf * chain preceded with a tag value of '2'. * * S Pointer to a string. Store the string passed in into the mbuf * chain preceded with a tag value of '4'. Applied a unicode * conversion is appropriate. * * A Same as 'S' * * P Pointer to a string. Store the string passed in into the mbuf * chain preceded with a tag value of '5'. Applied a unicode * conversion is appropriate. * * u Pointer to a string. Store the string passed in into the mbuf * chain. Applied a unicode conversion is appropriate. * * s Pointer to a string. Store the string passed in into the mbuf * chain. * * Y Date/Time. Store the Date/Time or the number of Date/Time(s) * specified into the the mbuf chain. A format string like this * "2Y" would require 2 Date/Time values. The Date/Time is * converted to DOS before storing. * * y Same as 'Y'. The order of Date and Time is reversed. * * , Character. Store the character or number of character specified * into the mbuf chain. A format string like this "2c" would * require 2 characters to be passed in. A unicode conversion is * applied if appropriate. * * . Same as '`' without unicode conversion. * * U Align the offset of the mbuf chain on a 16bit boundary. */ int smb_mbc_vencodef(mbuf_chain_t *mbc, const char *fmt, va_list ap) { char *charp; uint8_t *cvalp; timestruc_t *tvp; smb_vdb_t *vdp; smb_request_t *sr = NULL; uint64_t llval; int64_t nt_time; uint32_t lval; uint_t tag; int unicode = 0; int repc; boolean_t repc_specified; uint16_t wval; uint8_t cval; uint8_t c; while ((c = *fmt++) != 0) { repc_specified = B_FALSE; repc = 1; if ('0' <= c && c <= '9') { repc = 0; do { repc = repc * 10 + c - '0'; c = *fmt++; } while ('0' <= c && c <= '9'); repc_specified = B_TRUE; } else if (c == '#') { repc = va_arg(ap, int); c = *fmt++; repc_specified = B_TRUE; } switch (c) { case '%': sr = va_arg(ap, struct smb_request *); if (sr->session->dialect >= SMB_VERS_2_BASE) { unicode = 1; break; } unicode = sr->smb_flg2 & SMB_FLAGS2_UNICODE; break; case 'C': /* Mbuf_chain */ if (mbc_marshal_put_mbuf_chain(mbc, va_arg(ap, mbuf_chain_t *)) != 0) return (DECODE_NO_MORE_DATA); break; case 'D': vdp = va_arg(ap, struct vardata_block *); if (mbc_marshal_put_char(mbc, 1) != 0) return (DECODE_NO_MORE_DATA); if (mbc_marshal_put_short(mbc, vdp->vdb_len) != 0) return (DECODE_NO_MORE_DATA); if (mbc_marshal_put_uio(mbc, &vdp->vdb_uio) != 0) return (DECODE_NO_MORE_DATA); break; case 'M': /* 0xFF S M B */ if (mbc_marshal_put_long(mbc, 0x424D53FF)) return (DECODE_NO_MORE_DATA); break; case 'N': /* 0xFE S M B */ if (mbc_marshal_put_long(mbc, 0x424D53FE)) return (DECODE_NO_MORE_DATA); break; case 'T': tvp = va_arg(ap, timestruc_t *); nt_time = smb_time_unix_to_nt(tvp); if (mbc_marshal_put_long_long(mbc, nt_time) != 0) return (DECODE_NO_MORE_DATA); break; case 'V': vdp = va_arg(ap, struct vardata_block *); if (mbc_marshal_put_char(mbc, 5) != 0) return (DECODE_NO_MORE_DATA); if (mbc_marshal_put_short(mbc, vdp->vdb_len) != 0) return (DECODE_NO_MORE_DATA); if (mbc_marshal_put_uio(mbc, &vdp->vdb_uio) != 0) return (DECODE_NO_MORE_DATA); break; case 'b': while (repc-- > 0) { cval = va_arg(ap, int); if (mbc_marshal_put_char(mbc, cval) != 0) return (DECODE_NO_MORE_DATA); } break; case 'm': /* struct_mbuf */ if (mbc_marshal_put_mbufs(mbc, va_arg(ap, mbuf_t *)) != 0) return (DECODE_NO_MORE_DATA); break; case 'c': cvalp = va_arg(ap, uint8_t *); while (repc-- > 0) { if (mbc_marshal_put_char(mbc, *cvalp++) != 0) return (DECODE_NO_MORE_DATA); } break; case 'w': while (repc-- > 0) { wval = va_arg(ap, int); if (mbc_marshal_put_short(mbc, wval) != 0) return (DECODE_NO_MORE_DATA); } break; case 'l': while (repc-- > 0) { lval = va_arg(ap, uint32_t); if (mbc_marshal_put_long(mbc, lval) != 0) return (DECODE_NO_MORE_DATA); } break; case 'q': while (repc-- > 0) { llval = va_arg(ap, uint64_t); if (mbc_marshal_put_long_long(mbc, llval) != 0) return (DECODE_NO_MORE_DATA); } break; case 'L': tag = 2; goto oem_conversion; case 'S': case 'A': tag = 4; goto tagged_str; case 'P': tag = 3; goto tagged_str; tagged_str: if (mbc_marshal_put_char(mbc, tag) != 0) return (DECODE_NO_MORE_DATA); /* FALLTHROUGH */ case 'u': /* Convert from unicode if flags are set */ if (unicode) goto unicode_translation; /* FALLTHROUGH */ case 's': /* put OEM string */ oem_conversion: charp = va_arg(ap, char *); if (!repc_specified) repc = 0; if (mbc_marshal_put_oem_string(mbc, charp, repc) != 0) return (DECODE_NO_MORE_DATA); break; case 'U': /* put UTF-16 string */ unicode_translation: charp = va_arg(ap, char *); if (!repc_specified) repc = 0; if (mbc_marshal_put_unicode_string(mbc, charp, repc) != 0) return (DECODE_NO_MORE_DATA); break; case 'Y': /* int32_t, encode dos date/time */ while (repc-- > 0) { uint16_t d, t; lval = va_arg(ap, uint32_t); smb_time_unix_to_dos(lval, (short *)&d, (short *)&t); if (mbc_marshal_put_short(mbc, t) != 0) return (DECODE_NO_MORE_DATA); if (mbc_marshal_put_short(mbc, d) != 0) return (DECODE_NO_MORE_DATA); } break; case 'y': /* int32_t, encode dos date/time */ while (repc-- > 0) { uint16_t d, t; lval = va_arg(ap, uint32_t); smb_time_unix_to_dos(lval, (short *)&d, (short *)&t); if (mbc_marshal_put_short(mbc, d) != 0) return (DECODE_NO_MORE_DATA); if (mbc_marshal_put_short(mbc, t) != 0) return (DECODE_NO_MORE_DATA); } break; case ',': if (unicode) repc *= 2; /* FALLTHROUGH */ case '.': while (repc-- > 0) if (mbc_marshal_put_char(mbc, 0) != 0) return (DECODE_NO_MORE_DATA); break; default: ASSERT(0); return (-1); } } return (0); } /* * smb_mbc_encodef * * This function builds a stream of bytes in the mbc chain passed in under the * control of the format fmt. * * (for a description of the format string see smb_mbc_vencodef()). */ int smb_mbc_encodef(mbuf_chain_t *mbc, const char *fmt, ...) { int rc; va_list ap; va_start(ap, fmt); rc = smb_mbc_vencodef(mbc, fmt, ap); va_end(ap); return (rc); } /* * smb_mbc_poke * * This function writes a stream of bytes in the mbc passed in at the specified * offset under the control of the format fmt. The offset of the chain passed in * is not modified. * * (for a description of the format string see smb_mbc_vencodef()). */ int smb_mbc_poke(mbuf_chain_t *mbc, int offset, const char *fmt, ...) { int len, rc; mbuf_chain_t tmp; va_list ap; if ((len = mbc->max_bytes - offset) < 0) return (DECODE_NO_MORE_DATA); rc = MBC_SHADOW_CHAIN(&tmp, mbc, offset, len); if (rc) return (DECODE_NO_MORE_DATA); va_start(ap, fmt); rc = smb_mbc_vencodef(&tmp, fmt, ap); va_end(ap); return (rc); } /* * Copy data from the src mbuf chain to the dst mbuf chain, * at the given offset in the src and current offset in dst, * for copy_len bytes. Does NOT update src->chain_offset. */ int smb_mbc_copy(mbuf_chain_t *dst_mbc, const mbuf_chain_t *src_mbc, int copy_offset, int copy_len) { mbuf_t *src_m; int offset, len; int rc; if (copy_len <= 0) return (0); if (copy_offset < 0) return (EINVAL); if ((copy_offset + copy_len) > src_mbc->max_bytes) return (EMSGSIZE); /* * Advance to the src mbuf where we start copying. */ offset = copy_offset; src_m = src_mbc->chain; while (src_m && offset >= src_m->m_len) { offset -= src_m->m_len; src_m = src_m->m_next; } if (src_m == NULL) return (EFAULT); /* * Copy the first part, which may start somewhere past * the beginning of the current mbuf. */ len = src_m->m_len - offset; if (len > copy_len) len = copy_len; rc = smb_mbc_put_mem(dst_mbc, src_m->m_data + offset, len); if (rc != 0) return (rc); copy_len -= len; /* * Copy remaining mbufs... */ while (copy_len > 0) { src_m = src_m->m_next; if (src_m == NULL) break; len = src_m->m_len; if (len > copy_len) len = copy_len; rc = smb_mbc_put_mem(dst_mbc, src_m->m_data, len); copy_len -= len; } return (0); } /* * Copy data from the passed memory buffer into the mbuf chain * at the current offset. */ int smb_mbc_put_mem(mbuf_chain_t *mbc, void *vmem, int mem_len) { caddr_t mem = vmem; mbuf_t *m; int32_t offset, tlen; int rc; if (mem_len <= 0) return (0); if ((rc = mbc_marshal_make_room(mbc, mem_len)) != 0) return (rc); /* * Advance to the dst mbuf where we start copying. * Allocations were done by _make_room(). */ offset = mbc->chain_offset; m = mbc->chain; while (offset >= m->m_len) { ASSERT(m->m_len > 0); offset -= m->m_len; m = m->m_next; } /* * Copy the first part, which may start somewhere past * the beginning of the current mbuf. */ tlen = m->m_len - offset; if (tlen > mem_len) tlen = mem_len; bcopy(mem, m->m_data + offset, tlen); mbc->chain_offset += tlen; mem += tlen; mem_len -= tlen; /* * Copy remaining mem into mbufs. These all start * at the beginning of each mbuf, and the last may * end somewhere short of m_len. */ while (mem_len > 0) { m = m->m_next; tlen = m->m_len; if (tlen > mem_len) tlen = mem_len; bcopy(mem, m->m_data, tlen); mbc->chain_offset += tlen; mem += tlen; mem_len -= tlen; } return (0); } /* * Put padding sufficient to align to A, where * A is some power of 2 greater than zero. */ int smb_mbc_put_align(mbuf_chain_t *mbc, int align) { int mask = align - 1; int padsz; ASSERT(align > 0 && (align & mask) == 0); if ((mbc->chain_offset & mask) == 0) return (0); padsz = align - (mbc->chain_offset & mask); return (smb_mbc_encodef(mbc, "#.", padsz)); } /* * Put data into mbuf chain allocating as needed. * Adds room to end of mbuf chain if needed. */ static int mbc_marshal_make_room(mbuf_chain_t *mbc, int32_t bytes_needed) { mbuf_t *m; mbuf_t *last; int32_t bytes_available; bytes_needed += mbc->chain_offset; if (bytes_needed > mbc->max_bytes) return (EMSGSIZE); /* * First mbuf in chain should have prepend space. * See M_LEADINGSPACE() below. */ if ((m = mbc->chain) == NULL) { MGET(m, M_WAIT, MT_DATA); m->m_len = 0; MCLGET(m, M_WAIT); m->m_data += MH_PREPEND_SPACE; mbc->chain = m; /* xxxx */ /* ^ */ } /* ---- ----- --xx ---xxx */ /* ^ */ last = NULL; while ((m != NULL) && (bytes_needed >= m->m_len)) { last = m; bytes_needed -= m->m_len; m = m->m_next; } if ((bytes_needed == 0) || (m != NULL)) { /* We have enough room already */ return (0); } /* ---- ----- --xx ---xxx */ /* ^ */ /* Back up to start of last mbuf */ m = last; bytes_needed += m->m_len; /* ---- ----- --xx ---xxx */ /* ^ */ bytes_available = M_SIZE(m) - M_LEADINGSPACE(m); /* ---- ----- --xx ---xxx */ /* ^ */ while ((bytes_needed != 0) && (bytes_needed > bytes_available)) { m->m_len = bytes_available; bytes_needed -= m->m_len; /* ---- ----- --xx ------ */ /* ^ */ MGET(m->m_next, M_WAIT, MT_DATA); m = m->m_next; m->m_len = 0; MCLGET(m, M_WAIT); ASSERT(M_LEADINGSPACE(m) == 0); bytes_available = M_SIZE(m); /* ---- ----- --xx ------ xxxx */ /* ^ */ } /* ---- ----- --xx ------ xxxx */ /* ^ */ /* Expand last tail as needed */ if (m->m_len <= bytes_needed) { m->m_len = bytes_needed; /* ---- ----- --xx ------ --xx */ /* ^ */ } return (0); } static void mbc_marshal_store_byte(mbuf_chain_t *mbc, uint8_t data) { mbuf_t *m = mbc->chain; int32_t cur_offset = mbc->chain_offset; /* * Scan forward looking for the last data currently in chain. */ while (cur_offset >= m->m_len) { cur_offset -= m->m_len; m = m->m_next; } ((char *)m->m_data)[cur_offset] = data; mbc->chain_offset++; } static int mbc_marshal_put_char(mbuf_chain_t *mbc, uint8_t data) { if (mbc_marshal_make_room(mbc, sizeof (char)) != 0) return (DECODE_NO_MORE_DATA); mbc_marshal_store_byte(mbc, data); return (0); } static int mbc_marshal_put_short(mbuf_chain_t *mbc, uint16_t data) { if (mbc_marshal_make_room(mbc, sizeof (short))) return (DECODE_NO_MORE_DATA); mbc_marshal_store_byte(mbc, data); mbc_marshal_store_byte(mbc, data >> 8); return (0); } static int mbc_marshal_put_long(mbuf_chain_t *mbc, uint32_t data) { if (mbc_marshal_make_room(mbc, sizeof (int32_t))) return (DECODE_NO_MORE_DATA); mbc_marshal_store_byte(mbc, data); mbc_marshal_store_byte(mbc, data >> 8); mbc_marshal_store_byte(mbc, data >> 16); mbc_marshal_store_byte(mbc, data >> 24); return (0); } static int mbc_marshal_put_long_long(mbuf_chain_t *mbc, uint64_t data) { if (mbc_marshal_make_room(mbc, sizeof (int64_t))) return (DECODE_NO_MORE_DATA); mbc_marshal_store_byte(mbc, data); mbc_marshal_store_byte(mbc, data >> 8); mbc_marshal_store_byte(mbc, data >> 16); mbc_marshal_store_byte(mbc, data >> 24); mbc_marshal_store_byte(mbc, data >> 32); mbc_marshal_store_byte(mbc, data >> 40); mbc_marshal_store_byte(mbc, data >> 48); mbc_marshal_store_byte(mbc, data >> 56); return (0); } /* * 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 mbc data as necessary. * See also: msgbuf_put_oem_string */ static int mbc_marshal_put_oem_string(mbuf_chain_t *mbc, char *mbs, int repc) { uint8_t *oembuf = NULL; uint8_t *s; int oemlen; int rlen; int rc; /* * Compute length of converted OEM string, * NOT including null terminator */ if ((oemlen = smb_sbequiv_strlen(mbs)) == -1) return (DECODE_NO_MORE_DATA); /* * If repc not specified, put whole string + NULL, * otherwise will truncate or pad as needed. */ if (repc <= 0) repc = oemlen + 1; /* * Convert into a temporary buffer * Free oembuf before return. */ oembuf = smb_mem_zalloc(oemlen + 1); ASSERT(oembuf != NULL); rlen = smb_mbstooem(oembuf, mbs, oemlen); if (rlen < 0) { rc = DECODE_NO_MORE_DATA; goto out; } 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) { if (mbc_marshal_make_room(mbc, 1)) { rc = DECODE_NO_MORE_DATA; goto out; } mbc_marshal_store_byte(mbc, *s); if (*s != '\0') s++; repc--; } rc = 0; out: if (oembuf != NULL) smb_mem_free(oembuf); return (rc); } /* * 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 mbc data as necessary. * See also: msgbuf_put_unicode_string */ static int mbc_marshal_put_unicode_string(mbuf_chain_t *mbc, char *mbs, int repc) { smb_wchar_t *wcsbuf = NULL; smb_wchar_t *wp; smb_wchar_t wchar; size_t wcslen, wcsbytes; size_t rlen; int rc; /* align to word boundary */ if (mbc->chain_offset & 1) { if (mbc_marshal_make_room(mbc, 1)) return (DECODE_NO_MORE_DATA); mbc_marshal_store_byte(mbc, 0); } /* * Compute length of converted UTF-16 string, * NOT including null terminator (in bytes). */ wcsbytes = smb_wcequiv_strlen(mbs); if (wcsbytes == (size_t)-1) return (DECODE_NO_MORE_DATA); /* * If repc not specified, put whole string + NULL, * otherwise will truncate or pad as needed. */ if (repc <= 0) repc = wcsbytes + 2; /* * Convert into a temporary buffer * Free wcsbuf before return. */ wcslen = wcsbytes / 2; wcsbuf = smb_mem_zalloc(wcsbytes + 2); ASSERT(wcsbuf != NULL); rlen = smb_mbstowcs(wcsbuf, mbs, wcslen); if (rlen == (size_t)-1) { rc = DECODE_NO_MORE_DATA; goto out; } 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 >= sizeof (smb_wchar_t)) { if (mbc_marshal_make_room(mbc, sizeof (smb_wchar_t))) { rc = DECODE_NO_MORE_DATA; goto out; } wchar = LE_IN16(wp); mbc_marshal_store_byte(mbc, wchar); mbc_marshal_store_byte(mbc, wchar >> 8); if (wchar != 0) wp++; repc -= sizeof (smb_wchar_t); } if (repc > 0) { if (mbc_marshal_make_room(mbc, 1)) { rc = DECODE_NO_MORE_DATA; goto out; } mbc_marshal_store_byte(mbc, 0); } rc = 0; out: if (wcsbuf != NULL) smb_mem_free(wcsbuf); return (rc); } static void /*ARGSUSED*/ uiorefnoop(mbuf_t *m) { } static int mbc_marshal_put_uio(mbuf_chain_t *mbc, struct uio *uio) { mbuf_t **t; mbuf_t *m = NULL; struct iovec *iov = uio->uio_iov; int32_t i, iov_cnt = uio->uio_iovcnt; iov = uio->uio_iov; t = &mbc->chain; for (i = 0; i < iov_cnt; i++) { MGET(m, M_WAIT, MT_DATA); m->m_ext.ext_buf = iov->iov_base; m->m_ext.ext_free = uiorefnoop; m->m_data = m->m_ext.ext_buf; m->m_flags |= M_EXT; m->m_len = m->m_ext.ext_size = iov->iov_len; mbc->max_bytes += m->m_len; m->m_next = 0; *t = m; t = &m->m_next; iov++; } return (0); } int smb_mbuf_put_copy_threshold = 128; /* * Append an mbuf to the chain at chain_offset, with some optimizations: * If the chain is empty, just set the head (done). * If m is no larger than the copy threshold, copy. * Always consumes (or free's) the mbuf passed in. */ static int mbc_marshal_put_mbufs(mbuf_chain_t *mbc, mbuf_t *mbuf) { mbuf_t *m; int bytes, rc; /* * Length of mbuf(s) to be appended */ bytes = 0; m = mbuf; while (m != NULL) { bytes += m->m_len; m = m->m_next; } if (bytes == 0) { m_freem(mbuf); return (0); } /* * Check for space vs max_bytes */ if (!MBC_ROOM_FOR(mbc, bytes)) { m_freem(mbuf); return (EMSGSIZE); } /* * Empty mbc? (probably rare) */ if (mbc->chain == NULL) { mbc->chain = mbuf; mbc->chain_offset = bytes; return (0); } /* * Copy optimization. We've already checked that there's room * for the _put_mem operations, and that's the only error case * for that call, so just assert success and continue. */ if (bytes <= smb_mbuf_put_copy_threshold) { m = mbuf; while (m != NULL) { rc = smb_mbc_put_mem(mbc, m->m_data, m->m_len); ASSERT3S(rc, ==, 0); m = m_free(m); } return (0); } /* * Trim existing chain and append */ smb_mbuf_trim(mbc->chain, mbc->chain_offset); m = mbc->chain; while (m->m_next != NULL) m = m->m_next; m->m_next = mbuf; mbc->chain_offset += bytes; return (0); } /* * Append a new mbc (nmbc) to the existing chain mbc * Assumes the new mbc has been "trimmed" to length. * (This ignores nmbc.chain_offset) * * Always consume or dispose of nmbc->chain */ static int mbc_marshal_put_mbuf_chain(mbuf_chain_t *mbc, mbuf_chain_t *nmbc) { int rc = 0; if (nmbc->chain != NULL) { rc = mbc_marshal_put_mbufs(mbc, nmbc->chain); nmbc->chain = NULL; } return (rc); } static uint8_t mbc_marshal_fetch_byte(mbuf_chain_t *mbc) { uint8_t data; mbuf_t *m = mbc->chain; int32_t offset = mbc->chain_offset; while (offset >= m->m_len) { offset -= m->m_len; m = m->m_next; } data = ((uint8_t *)m->m_data)[offset]; mbc->chain_offset++; return (data); } static int mbc_marshal_get_char(mbuf_chain_t *mbc, uint8_t *data) { if (MBC_ROOM_FOR(mbc, sizeof (char)) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } *data = mbc_marshal_fetch_byte(mbc); return (0); } static int mbc_marshal_get_short(mbuf_chain_t *mbc, uint16_t *data) { uint16_t tmp; mbuf_t *m = mbc->chain; int32_t offset = mbc->chain_offset; if (MBC_ROOM_FOR(mbc, sizeof (short)) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } while (offset >= m->m_len) { offset -= m->m_len; m = m->m_next; } if ((m->m_len - offset) >= sizeof (short)) { *data = LE_IN16(m->m_data + offset); mbc->chain_offset += sizeof (short); } else { tmp = (uint16_t)mbc_marshal_fetch_byte(mbc); tmp |= ((uint16_t)mbc_marshal_fetch_byte(mbc)) << 8; *data = tmp; } return (0); } static int mbc_marshal_get_long(mbuf_chain_t *mbc, uint32_t *data) { uint32_t tmp; mbuf_t *m = mbc->chain; int32_t offset = mbc->chain_offset; if (MBC_ROOM_FOR(mbc, sizeof (int32_t)) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } while (offset >= m->m_len) { offset -= m->m_len; m = m->m_next; } if ((m->m_len - offset) >= sizeof (int32_t)) { *data = LE_IN32(m->m_data + offset); mbc->chain_offset += sizeof (int32_t); } else { tmp = (uint32_t)mbc_marshal_fetch_byte(mbc); tmp |= ((uint32_t)mbc_marshal_fetch_byte(mbc)) << 8; tmp |= ((uint32_t)mbc_marshal_fetch_byte(mbc)) << 16; tmp |= ((uint32_t)mbc_marshal_fetch_byte(mbc)) << 24; *data = tmp; } return (0); } static uint64_t qswap(uint64_t ll) { uint64_t v; v = ll >> 32; v |= ll << 32; return (v); } static int mbc_marshal_get_odd_long_long(mbuf_chain_t *mbc, uint64_t *data) { uint64_t tmp; mbuf_t *m = mbc->chain; int32_t offset = mbc->chain_offset; if (MBC_ROOM_FOR(mbc, sizeof (int64_t)) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } while (offset >= m->m_len) { offset -= m->m_len; m = m->m_next; } if ((m->m_len - offset) >= sizeof (int64_t)) { *data = qswap(LE_IN64(m->m_data + offset)); mbc->chain_offset += sizeof (int64_t); } else { tmp = (uint64_t)mbc_marshal_fetch_byte(mbc) << 32; tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 40; tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 48; tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 56; tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc); tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 8; tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 16; tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 24; *(uint64_t *)data = tmp; } return (0); } static int mbc_marshal_get_long_long(mbuf_chain_t *mbc, uint64_t *data) { uint64_t tmp; mbuf_t *m = mbc->chain; int32_t offset = mbc->chain_offset; if (MBC_ROOM_FOR(mbc, sizeof (int64_t)) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } while (offset >= m->m_len) { offset -= m->m_len; m = m->m_next; } if ((m->m_len - offset) >= sizeof (int64_t)) { *data = LE_IN64(m->m_data + offset); mbc->chain_offset += sizeof (int64_t); } else { tmp = (uint32_t)mbc_marshal_fetch_byte(mbc); tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 8; tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 16; tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 24; tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 32; tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 40; tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 48; tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 56; *(uint64_t *)data = tmp; } return (0); } /* * mbc_marshal_get_oem_string * * Decode an OEM string, returning its UTF-8 form in strpp, * allocated using smb_srm_zalloc (automatically freed). * If max_bytes != 0, consume at most max_bytes of the mbc. * See also: msgbuf_get_oem_string */ static int mbc_marshal_get_oem_string(smb_request_t *sr, mbuf_chain_t *mbc, char **strpp, int max_bytes) { char *mbs; uint8_t *oembuf = NULL; int oemlen, oemmax; int mbsmax; int rlen; int rc; if (max_bytes == 0) max_bytes = 0xffff; /* * Get the OtW data into a temporary buffer. * Free oembuf before return. */ oemlen = 0; oemmax = MALLOC_QUANTUM; oembuf = smb_mem_alloc(oemmax); for (;;) { uint8_t ch; if (oemlen >= max_bytes) break; if ((oemlen + 2) >= oemmax) { oemmax += MALLOC_QUANTUM; oembuf = smb_mem_realloc(oembuf, oemmax); } if (mbc_marshal_get_char(mbc, &ch) != 0) { rc = DECODE_NO_MORE_DATA; goto out; } if (ch == 0) break; oembuf[oemlen++] = ch; } 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_srm_zalloc(sr, mbsmax + 1); ASSERT(mbs != NULL); rlen = smb_oemtombs(mbs, oembuf, mbsmax); if (rlen < 0) { rc = DECODE_NO_MORE_DATA; goto out; } if (rlen > mbsmax) rlen = mbsmax; mbs[rlen] = '\0'; *strpp = mbs; rc = 0; out: if (oembuf != NULL) smb_mem_free(oembuf); return (rc); } /* * mbc_marshal_get_unicode_string * * Decode a UTF-16 string, returning its UTF-8 form in strpp, * allocated using smb_srm_zalloc (automatically freed). * If max_bytes != 0, consume at most max_bytes of the mbc. * See also: msgbuf_get_unicode_string */ static int mbc_marshal_get_unicode_string(smb_request_t *sr, mbuf_chain_t *mbc, char **strpp, int max_bytes) { char *mbs; uint16_t *wcsbuf = NULL; int wcslen; // wchar count int wcsmax; // byte count size_t mbsmax; size_t rlen; int rc; if (max_bytes == 0) max_bytes = 0xffff; /* * Unicode strings are always word aligned. */ if (mbc->chain_offset & 1) { if (MBC_ROOM_FOR(mbc, sizeof (char)) == 0) return (DECODE_NO_MORE_DATA); mbc->chain_offset++; } /* * Get the OtW data into a temporary buffer. * Free wcsbuf before return. */ wcslen = 0; wcsmax = MALLOC_QUANTUM; wcsbuf = smb_mem_alloc(wcsmax); for (;;) { uint16_t wchar; if ((wcslen * 2) >= max_bytes) break; if (((wcslen * 2) + 4) >= wcsmax) { wcsmax += MALLOC_QUANTUM; wcsbuf = smb_mem_realloc(wcsbuf, wcsmax); } if (mbc_marshal_get_short(mbc, &wchar) != 0) { rc = DECODE_NO_MORE_DATA; goto out; } if (wchar == 0) break; /* Keep in little-endian form. */ LE_OUT16(wcsbuf + wcslen, wchar); wcslen++; } 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_srm_zalloc(sr, mbsmax + 1); ASSERT(mbs != NULL); rlen = smb_wcstombs(mbs, wcsbuf, mbsmax); if (rlen == (size_t)-1) { rc = DECODE_NO_MORE_DATA; goto out; } if (rlen > mbsmax) rlen = mbsmax; mbs[rlen] = '\0'; *strpp = mbs; rc = 0; out: if (wcsbuf != NULL) smb_mem_free(wcsbuf); return (rc); } static int /*ARGSUSED*/ mbc_marshal_get_mbufs(mbuf_chain_t *mbc, int32_t bytes, mbuf_t **m) { *m = NULL; if (MBC_ROOM_FOR(mbc, bytes) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } /* not yet implemented */ return (-1); } static int mbc_marshal_get_mbuf_chain(mbuf_chain_t *mbc, int32_t bytes, mbuf_chain_t *nmbc) { int rc; mbuf_t *m; if (bytes == 0) { /* Get all the rest */ bytes = mbc->max_bytes - mbc->chain_offset; } MBC_SETUP(nmbc, mbc->max_bytes); if ((rc = mbc_marshal_get_mbufs(mbc, bytes, &m)) != 0) { if (m) m_freem(m); return (rc); } nmbc->chain = m; while (m != 0) { bytes += m->m_len; m = m->m_next; } nmbc->max_bytes = bytes; return (0); } static int mbc_marshal_get_uio(mbuf_chain_t *mbc, struct uio *uio) { int i, offset; int32_t bytes = uio->uio_resid; int32_t remainder; struct iovec *iov; mbuf_t *m; /* * The residual count is tested because in the case of write requests * with no data (smbtorture RAW-WRITE test will generate that type of * request) this function is called with a residual count of zero * bytes. */ if (bytes != 0) { iov = uio->uio_iov; uio->uio_segflg = UIO_SYSSPACE; uio->uio_extflg = UIO_COPY_DEFAULT; if (MBC_ROOM_FOR(mbc, bytes) == 0) { /* Data will never be available */ return (DECODE_NO_MORE_DATA); } m = mbc->chain; offset = mbc->chain_offset; while (offset >= m->m_len) { offset -= m->m_len; m = m->m_next; ASSERT((offset == 0) || (offset && m)); } for (i = 0; (bytes > 0) && (i < uio->uio_iovcnt); i++) { iov[i].iov_base = &m->m_data[offset]; remainder = m->m_len - offset; if (remainder >= bytes) { iov[i].iov_len = bytes; mbc->chain_offset += bytes; uio->uio_iovcnt = i + 1; return (0); } iov[i].iov_len = remainder; mbc->chain_offset += remainder; bytes -= remainder; m = m->m_next; offset = 0; } return (DECODE_NO_MORE_DATA); } return (0); } static int mbc_marshal_get_skip(mbuf_chain_t *mbc, uint_t skip) { if (MBC_ROOM_FOR(mbc, skip) == 0) return (DECODE_NO_MORE_DATA); mbc->chain_offset += skip; return (0); }