struct stream_io {
char *base;
char *xattr_name;
+ char *raw_stream_name;
void *fsp_name_ptr;
files_struct *fsp;
vfs_handle_struct *handle;
};
+/*
+ * Implement larger streams than fit into a single xattr by spreading
+ * the data over multiple xattrs.
+ *
+ * The anchor xattr remains the same: It stores stream data plus one
+ * byte to cope with zero-length streams where xattrs must store at
+ * least one byte. Default short streams store the value 0 in this
+ * last byte. If there are additional "extents", this last byte stores
+ * the number of extents. The extents don't come with the appended
+ * byte, we just don't store an xattr if not required.
+ */
+
+static char *streams_xattr_ext_name(TALLOC_CTX *mem_ctx,
+ const struct streams_xattr_config *config,
+ const char *raw_stream_name,
+ size_t extent_nr)
+{
+ char *name = NULL;
+
+ if (extent_nr == 0) {
+ name = talloc_asprintf(mem_ctx,
+ "%s%s",
+ config->prefix,
+ raw_stream_name);
+ } else {
+ name = talloc_asprintf(mem_ctx,
+ "%s%zu.%s",
+ config->ext_prefix,
+ extent_nr,
+ raw_stream_name);
+ }
+
+ return name;
+}
+
static ssize_t fgetxattr_multi(const struct streams_xattr_config *config,
struct files_struct *fsp,
- const char *name,
- void *value,
+ const char *raw_stream_name,
+ void *_value,
size_t size)
{
- ssize_t ret = SMB_VFS_FGETXATTR(fsp, name, value, size);
- return ret;
+ const size_t fraglen = lp_smbd_max_xattr_size(SNUM(fsp->conn));
+ uint8_t *value = NULL;
+ size_t valuelen;
+ ssize_t ret = -1;
+ char *xattr_name = NULL;
+ uint8_t i, marker;
+
+ xattr_name = streams_xattr_ext_name(talloc_tos(),
+ config,
+ raw_stream_name,
+ 0);
+ if (xattr_name == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ value = talloc_array(talloc_tos(), uint8_t, fraglen);
+ if (value == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ ret = SMB_VFS_FGETXATTR(fsp, xattr_name, value, fraglen);
+ if (ret < 0) {
+ goto fail;
+ }
+ if ((ret == 0) || ((size_t)ret > fraglen)) {
+ errno = EINVAL;
+ goto fail;
+ }
+ TALLOC_FREE(xattr_name);
+
+ valuelen = ret;
+
+ marker = value[valuelen - 1];
+ valuelen -= 1; /* Re-add marker later */
+
+ if (marker > config->max_extents) {
+ errno = EINVAL;
+ goto fail;
+ }
+
+ for (i = 0; i < marker; i++) {
+ uint8_t *tmp = NULL;
+
+ tmp = talloc_realloc(talloc_tos(),
+ value,
+ uint8_t,
+ valuelen + fraglen);
+ if (tmp == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+ value = tmp;
+
+ xattr_name = streams_xattr_ext_name(talloc_tos(),
+ config,
+ raw_stream_name,
+ i + 1);
+ if (xattr_name == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ ret = SMB_VFS_FGETXATTR(fsp,
+ xattr_name,
+ value + valuelen,
+ fraglen);
+ if (ret < 0) {
+ if (errno == ENODATA) {
+ /*
+ * Happens if fsetxattr_multi could not write
+ * everything it intended to write. Return a
+ * short read.
+ */
+ TALLOC_FREE(xattr_name);
+ break;
+ }
+ goto fail;
+ }
+
+ if ((ret == 0) || ((size_t)ret > fraglen)) {
+ errno = EINVAL;
+ goto fail;
+ }
+
+ valuelen += ret;
+ if (valuelen < (size_t)ret) {
+ errno = EOVERFLOW;
+ goto fail;
+ }
+
+ TALLOC_FREE(xattr_name);
+ }
+
+ if (valuelen == SIZE_MAX) {
+ errno = EOVERFLOW;
+ goto fail;
+ }
+ if (valuelen+1 > size) {
+ errno = ERANGE;
+ goto fail;
+ }
+
+ memcpy(_value, value, valuelen);
+ TALLOC_FREE(value);
+ ((uint8_t *)_value)[valuelen] = 0; /* marker */
+
+ return valuelen+1;
+
+fail:
+ {
+ int err = errno;
+ TALLOC_FREE(value);
+ TALLOC_FREE(xattr_name);
+ errno = err;
+ }
+ return -1;
+}
+
+static size_t round_up(size_t len, size_t fraglen)
+{
+ return ((len + fraglen - 1) / fraglen) * fraglen;
}
static int fsetxattr_multi(const struct streams_xattr_config *config,
struct files_struct *fsp,
- const char *name,
+ const char *raw_stream_name,
const void *value,
size_t size,
int flags)
{
- int ret = SMB_VFS_FSETXATTR(fsp, name, value, size, flags);
+ const size_t fraglen = lp_smbd_max_xattr_size(SNUM(fsp->conn));
+ uint8_t *frag = NULL;
+ size_t i, len_extents, num_extents, written;
+ int ret = -1;
+ char *xattr_name = NULL;
+
+ if (size < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ xattr_name = streams_xattr_ext_name(talloc_tos(),
+ config,
+ raw_stream_name,
+ 0);
+ if (xattr_name == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ if (size <= fraglen) {
+ uint8_t marker = ((const char *)value)[size - 1];
+
+ if (marker != 0) {
+ errno = EINVAL;
+ goto done;
+ }
+
+ ret = SMB_VFS_FSETXATTR(fsp, xattr_name, value, size, flags);
+ goto done;
+ }
+
+ size -= 1; /* remove marker byte */
+ len_extents = size - (fraglen - 1); /* marker in first frag */
+
+ num_extents = round_up(len_extents, fraglen) / fraglen;
+
+ if (num_extents > config->max_extents) {
+ errno = EOVERFLOW;
+ goto done;
+ }
+
+ frag = talloc_array(talloc_tos(), uint8_t, fraglen);
+ if (frag == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ memcpy(frag, value, fraglen - 1);
+ frag[fraglen - 1] = num_extents;
+
+ ret = SMB_VFS_FSETXATTR(fsp, xattr_name, frag, fraglen, flags);
+ if (ret == -1) {
+ goto done;
+ }
+ TALLOC_FREE(frag);
+ TALLOC_FREE(xattr_name);
+
+ written = fraglen - 1;
+
+ for (i = 0; i < config->max_extents; i++) {
+ size_t to_write = MIN(fraglen, size - written);
+
+ xattr_name = streams_xattr_ext_name(talloc_tos(),
+ config,
+ raw_stream_name,
+ i + 1);
+ if (xattr_name == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ if (to_write == 0) {
+ /*
+ * Need to remove possible leftovers from
+ * larger stream that was here before.
+ */
+ ret = SMB_VFS_FREMOVEXATTR(fsp, xattr_name);
+
+ if ((ret == -1) && (errno == ENODATA)) {
+ /*
+ * fsetxattr_multi writes an
+ * uninterrupted sequence from 1 to
+ * config->max_extents. If we can't
+ * remove one, it might be because
+ * it's not there (errno==ENODATA),
+ * then nothing will follow.
+ */
+ ret = 0;
+ break;
+ }
+
+ /*
+ * We're here because either we successfully
+ * removed a leftover, so try the next. Or we
+ * got a real error. Then also try the
+ * rest. The one we could not remove does not
+ * hurt the stream data, we've written
+ * everything we were asked to write.
+ *
+ */
+ continue;
+ }
+
+ ret = SMB_VFS_FSETXATTR(fsp,
+ xattr_name,
+ ((const char *)value) + written,
+ to_write,
+ flags);
+ TALLOC_FREE(xattr_name);
+ if (ret == -1) {
+ goto done;
+ }
+
+ written += to_write;
+ }
+
+ SMB_ASSERT(written == size);
+ ret = 0;
+
+done:
+ {
+ int err = errno;
+ TALLOC_FREE(frag);
+ TALLOC_FREE(xattr_name);
+ errno = err;
+ }
return ret;
}
static int fremovexattr_multi(const struct streams_xattr_config *config,
struct files_struct *fsp,
- const char *name)
+ const char *raw_stream_name)
{
- int ret = SMB_VFS_FREMOVEXATTR(fsp, name);
- return ret;
+ int ret;
+ char *xattr_name = NULL;
+ size_t i;
+
+ xattr_name = streams_xattr_ext_name(talloc_tos(),
+ config,
+ raw_stream_name,
+ 0);
+ if (xattr_name == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_FREMOVEXATTR(fsp, xattr_name);
+ TALLOC_FREE(xattr_name);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ for (i = 0; i < config->max_extents; i++) {
+ xattr_name = streams_xattr_ext_name(talloc_tos(),
+ config,
+ raw_stream_name,
+ i + 1);
+ if (xattr_name == NULL) {
+ /*
+ * Return success. The main xattr is gone. All
+ * that happens now is that we leave orphaned
+ * and unreferenced xattrs around. Via this
+ * module these will never be referenced
+ * again, it will not create a smb-level data
+ * leak.
+ */
+ return 0;
+ }
+
+ ret = SMB_VFS_FREMOVEXATTR(fsp, xattr_name);
+
+ TALLOC_FREE(xattr_name);
+ if (ret < 0) {
+ if (errno == ENODATA) {
+ return 0;
+ }
+ return ret;
+ }
+ }
+
+ return 0;
}
static int streams_xattr_get_ea_value_fsp(
const char *ea_name,
char **_val)
{
- /* Get the value of this xattr. Max size is 64k. */
- size_t attr_size = 256;
+ size_t attr_size = lp_smbd_max_xattr_size(SNUM(fsp->conn));
+ size_t max_stream_size;
char *val = NULL;
ssize_t sizeret;
- size_t max_xattr_size = 0;
bool refuse;
if (fsp == NULL) {
return EACCES;
}
- max_xattr_size = lp_smbd_max_xattr_size(SNUM(fsp->conn));
+ if (attr_size > (SIZE_MAX / config->max_extents)) {
+ return EOVERFLOW;
+ }
+ max_stream_size = attr_size * config->max_extents;
+
+ max_stream_size += attr_size;
+ if (max_stream_size < attr_size) {
+ return EOVERFLOW;
+ }
again:
}
sizeret = fgetxattr_multi(config, fsp, ea_name, val, attr_size);
- if (sizeret == -1 && errno == ERANGE && attr_size < max_xattr_size) {
- attr_size = max_xattr_size;
+ if (sizeret == -1 && errno == ERANGE && attr_size < max_stream_size) {
+ attr_size = max_stream_size;
goto again;
}
static ssize_t get_xattr_size_fsp(const struct streams_xattr_config *config,
struct files_struct *fsp,
- const char *xattr_name)
+ const char *raw_stream_name)
{
int ret;
char *val = NULL;
ssize_t result;
ret = streams_xattr_get_ea_value_fsp(
- talloc_tos(), config, fsp, xattr_name, &val);
+ talloc_tos(), config, fsp, raw_stream_name, &val);
if (ret != 0) {
return -1;
}
result = talloc_get_size(val);
TALLOC_FREE(val);
- return result;
+ if (result < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ return result-1;
}
/**
static int streams_xattr_get_name(vfs_handle_struct *handle,
TALLOC_CTX *ctx,
const char *stream_name,
+ char **_raw_stream_name,
char **xattr_name)
{
size_t stream_name_len = strlen(stream_name);
char *stype;
struct streams_xattr_config *config;
+ char *raw_stream_name = NULL;
SMB_VFS_HANDLE_GET_DATA(handle,
config,
stream_name_len = (stype - stream_name);
}
- *xattr_name = talloc_asprintf(ctx, "%s%.*s%s",
+ raw_stream_name = talloc_asprintf(ctx,
+ "%.*s%s",
+ (int)stream_name_len,
+ stream_name,
+ config->store_stream_type ? ":$DATA"
+ : "");
+ if (raw_stream_name == NULL) {
+ return ENOMEM;
+ }
+
+ *xattr_name = talloc_asprintf(ctx,
+ "%s%s",
config->prefix,
- (int)stream_name_len,
- stream_name,
- config->store_stream_type ? ":$DATA" : "");
+ raw_stream_name);
if (*xattr_name == NULL) {
+ TALLOC_FREE(raw_stream_name);
return ENOMEM;
}
+ *_raw_stream_name = raw_stream_name;
+
DBG_DEBUG("%s, stream_name: %s\n", *xattr_name, stream_name);
return 0;
{
int ret;
char *xattr_name = NULL;
+ char *raw_stream_name = NULL;
if (sio->fsp->fsp_name == sio->fsp_name_ptr) {
return true;
ret = streams_xattr_get_name(sio->handle,
talloc_tos(),
sio->fsp->fsp_name->stream_name,
+ &raw_stream_name,
&xattr_name);
if (ret != 0) {
return false;
}
TALLOC_FREE(sio->xattr_name);
+ TALLOC_FREE(sio->raw_stream_name);
TALLOC_FREE(sio->base);
sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
xattr_name);
}
TALLOC_FREE(xattr_name);
+ sio->raw_stream_name = talloc_strdup(
+ VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
+ raw_stream_name);
+ if (sio->raw_stream_name == NULL) {
+ DBG_DEBUG("sio->raw_stream_name==NULL\n");
+ return false;
+ }
+ TALLOC_FREE(raw_stream_name);
+
sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
sio->fsp->fsp_name->base_name);
if (sio->base == NULL) {
sbuf->st_ex_size = get_xattr_size_fsp(config,
fsp->base_fsp,
- io->xattr_name);
+ io->raw_stream_name);
if (sbuf->st_ex_size == -1) {
SET_STAT_INVALID(*sbuf);
return -1;
int ret;
int result = -1;
char *xattr_name = NULL;
+ char *raw_stream_name = NULL;
char *tmp_stream_name = NULL;
struct smb_filename *pathref = NULL;
struct files_struct *fsp = smb_fname->fsp;
ret = streams_xattr_get_name(handle,
talloc_tos(),
smb_fname->stream_name,
+ &raw_stream_name,
&xattr_name);
if (ret != 0) {
errno = ret;
fsp = fsp->base_fsp;
}
- smb_fname->st.st_ex_size = get_xattr_size_fsp(config, fsp, xattr_name);
+ smb_fname->st.st_ex_size = get_xattr_size_fsp(config,
+ fsp,
+ raw_stream_name);
if (smb_fname->st.st_ex_size == -1) {
TALLOC_FREE(xattr_name);
TALLOC_FREE(pathref);
{
struct streams_xattr_config *config = NULL;
char *xattr_name = NULL;
+ char *raw_stream_name = NULL;
struct smb_filename *pathref = NULL;
struct files_struct *fsp = smb_fname->fsp;
ssize_t size;
ret = streams_xattr_get_name(handle,
talloc_tos(),
smb_fname->stream_name,
+ &raw_stream_name,
&xattr_name);
if (ret != 0) {
errno = ret;
*sbuf = fsp->fsp_name->st;
- size = get_xattr_size_fsp(config, fsp, xattr_name);
+ size = get_xattr_size_fsp(config, fsp, raw_stream_name);
if (size == -1) {
errno = ENOENT;
ret = -1;
struct stream_io *sio = NULL;
char *val = NULL;
char *xattr_name = NULL;
+ char *raw_stream_name = NULL;
int fakefd = -1;
bool set_empty_xattr = false;
int ret;
ret = streams_xattr_get_name(handle,
talloc_tos(),
smb_fname->stream_name,
+ &raw_stream_name,
&xattr_name);
if (ret != 0) {
errno = ret;
}
ret = streams_xattr_get_ea_value_fsp(
- talloc_tos(), config, fsp->base_fsp, xattr_name, &val);
+ talloc_tos(), config, fsp->base_fsp, raw_stream_name, &val);
if (ret != 0) {
DBG_DEBUG("streams_xattr_get_ea_value_fsp returned %s\n",
strerror(ret));
ret = fsetxattr_multi(config,
fsp->base_fsp,
- xattr_name,
+ raw_stream_name,
&null,
sizeof(null),
how->flags & O_EXCL ? XATTR_CREATE : 0);
goto fail;
}
+ sio->raw_stream_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle,
+ fsp),
+ raw_stream_name);
+ if (sio->raw_stream_name == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
/*
* so->base needs to be a copy of fsp->fsp_name->base_name,
* making it identical to streams_xattr_recheck(). If the
ret = streams_xattr_get_name(handle,
talloc_tos(),
smb_fname->stream_name,
+ &raw_stream_name,
&xattr_name);
if (ret != 0) {
errno = ret;
fsp = fsp->base_fsp;
}
- ret = fremovexattr_multi(config, fsp, xattr_name);
+ ret = fremovexattr_multi(config, fsp, raw_stream_name);
if ((ret == -1) && (errno == ENOATTR)) {
errno = ENOENT;
NTSTATUS status;
int ret = -1;
char *src_xattr_name = NULL;
+ char *src_raw_stream_name = NULL;
char *dst_xattr_name = NULL;
+ char *dst_raw_stream_name = NULL;
bool src_is_stream, dst_is_stream;
ssize_t oret;
ssize_t nret;
ret = streams_xattr_get_name(handle,
talloc_tos(),
smb_fname_src->stream_name,
+ &src_raw_stream_name,
&src_xattr_name);
if (ret != 0) {
errno = ret;
ret = streams_xattr_get_name(handle,
talloc_tos(),
smb_fname_dst->stream_name,
+ &dst_raw_stream_name,
&dst_xattr_name);
if (ret != 0) {
errno = ret;
}
/* Read the old stream from the base file fsp. */
- ret = streams_xattr_get_ea_value_fsp(
- talloc_tos(), config, pathref_src->fsp, src_xattr_name, &val);
+ ret = streams_xattr_get_ea_value_fsp(talloc_tos(),
+ config,
+ pathref_src->fsp,
+ src_raw_stream_name,
+ &val);
if (ret != 0) {
errno = ret;
goto fail;
/* (Over)write the new stream on the base file fsp. */
nret = fsetxattr_multi(config,
pathref_dst->fsp,
- dst_xattr_name,
+ dst_raw_stream_name,
val,
talloc_get_size(val),
0);
/*
* Remove the old stream from the base file fsp.
*/
- oret = fremovexattr_multi(config, pathref_src->fsp, src_xattr_name);
+ oret = fremovexattr_multi(config,
+ pathref_src->fsp,
+ src_raw_stream_name);
if (oret < 0) {
if (errno == ENOATTR) {
errno = ENOENT;
}
ret = streams_xattr_get_ea_value_fsp(
- names, config, smb_fname->fsp, names[i], &val);
+ names,
+ config,
+ smb_fname->fsp,
+ names[i] + strlen(SAMBA_XATTR_DOSSTREAM_PREFIX),
+ &val);
if (ret != 0) {
DBG_DEBUG("Could not get ea %s for file %s: %s\n",
names[i],
(struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
char *val = NULL;
size_t len;
- int ret;
+ int max, ret;
SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config,
return -1);
return -1;
}
+ max = lp_smbd_max_xattr_size(SNUM(handle->conn));
+ if (max <= 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ max *= (config->max_extents + 1);
+
if (!streams_xattr_recheck(sio)) {
return -1;
}
- if ((offset + n) >= lp_smbd_max_xattr_size(SNUM(handle->conn))) {
+ if ((offset + n) >= (unsigned)max) {
/*
* Requested write is beyond what can be read based on
* samba configuration.
return -1;
}
- ret = streams_xattr_get_ea_value_fsp(
- talloc_tos(), config, fsp->base_fsp, sio->xattr_name, &val);
+ ret = streams_xattr_get_ea_value_fsp(talloc_tos(),
+ config,
+ fsp->base_fsp,
+ sio->raw_stream_name,
+ &val);
if (ret != 0) {
errno = ret;
return -1;
ret = fsetxattr_multi(config,
fsp->base_fsp,
- sio->xattr_name,
+ sio->raw_stream_name,
val,
talloc_get_size(val),
0);
return -1;
}
- ret = streams_xattr_get_ea_value_fsp(
- talloc_tos(), config, fsp->base_fsp, sio->xattr_name, &val);
+ ret = streams_xattr_get_ea_value_fsp(talloc_tos(),
+ config,
+ fsp->base_fsp,
+ sio->raw_stream_name,
+ &val);
if (ret != 0) {
errno = ret;
return -1;
return -1;
}
- ret = streams_xattr_get_ea_value_fsp(
- talloc_tos(), config, fsp->base_fsp, sio->xattr_name, &val);
+ ret = streams_xattr_get_ea_value_fsp(talloc_tos(),
+ config,
+ fsp->base_fsp,
+ sio->raw_stream_name,
+ &val);
if (ret != 0) {
errno = ret;
return -1;
ret = fsetxattr_multi(config,
fsp->base_fsp,
- sio->xattr_name,
+ sio->raw_stream_name,
val,
talloc_get_size(val),
0);