blob: 462cc65647503017cc7f2ebe7f4378bec66c83bd [file] [log] [blame]
/*
* Copyright (c) 1997-8,2021 Andrew G. Morgan <morgan@kernel.org>
*
* This file deals with exchanging internal and external
* representations of capability sets.
*/
#include "libcap.h"
/*
* External representation for capabilities. (exported as a fixed
* length)
*/
#define CAP_EXT_MAGIC "\220\302\001\121"
#define CAP_EXT_MAGIC_SIZE 4
const static __u8 external_magic[CAP_EXT_MAGIC_SIZE+1] = CAP_EXT_MAGIC;
/*
* This is the largest size libcap can currently export.
* cap_size() may return something smaller depending on the
* content of its argument cap_t.
*/
struct cap_ext_struct {
__u8 magic[CAP_EXT_MAGIC_SIZE];
__u8 length_of_capset;
/*
* note, we arrange these so the caps are stacked with byte-size
* resolution
*/
__u8 bytes[CAP_SET_SIZE][NUMBER_OF_CAP_SETS];
};
/*
* minimum exported flag size: libcap2 has always exported with flags
* this size.
*/
static size_t _libcap_min_ext_flag_size = CAP_SET_SIZE < 8 ? CAP_SET_SIZE : 8;
static ssize_t _cap_size_locked(cap_t cap_d)
{
size_t j, used;
for (j=used=0; j<CAP_SET_SIZE; j+=sizeof(__u32)) {
int i;
__u32 val = 0;
for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
val |= cap_d->u[j/sizeof(__u32)].flat[i];
}
if (val == 0) {
continue;
}
if (val > 0x0000ffff) {
if (val > 0x00ffffff) {
used = j+4;
} else {
used = j+3;
}
} else if (val > 0x000000ff) {
used = j+2;
} else {
used = j+1;
}
}
if (used < _libcap_min_ext_flag_size) {
used = _libcap_min_ext_flag_size;
}
return (ssize_t)(CAP_EXT_MAGIC_SIZE + 1+ NUMBER_OF_CAP_SETS * used);
}
/*
* return size of external capability set
*/
ssize_t cap_size(cap_t cap_d)
{
size_t used;
if (!good_cap_t(cap_d)) {
return ssizeof(struct cap_ext_struct);
}
_cap_mu_lock(&cap_d->mutex);
used = _cap_size_locked(cap_d);
_cap_mu_unlock(&cap_d->mutex);
return used;
}
/*
* Copy the internal (cap_d) capability set into an external
* representation. The external representation is portable to other
* Linux architectures.
*/
ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length)
{
struct cap_ext_struct *result = (struct cap_ext_struct *) cap_ext;
ssize_t csz, len_set;
int i;
/* valid arguments? */
if (!good_cap_t(cap_d) || cap_ext == NULL) {
errno = EINVAL;
return -1;
}
_cap_mu_lock(&cap_d->mutex);
csz = _cap_size_locked(cap_d);
if (csz > length) {
errno = EINVAL;
_cap_mu_unlock_return(&cap_d->mutex, -1);
}
len_set = (csz - (CAP_EXT_MAGIC_SIZE+1))/NUMBER_OF_CAP_SETS;
/* fill external capability set */
memcpy(&result->magic, external_magic, CAP_EXT_MAGIC_SIZE);
result->length_of_capset = len_set;
for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
size_t j;
for (j=0; j<len_set; ) {
__u32 val;
val = cap_d->u[j/sizeof(__u32)].flat[i];
result->bytes[j++][i] = val & 0xFF;
if (j < len_set) {
result->bytes[j++][i] = (val >>= 8) & 0xFF;
}
if (j < len_set) {
result->bytes[j++][i] = (val >>= 8) & 0xFF;
}
if (j < len_set) {
result->bytes[j++][i] = (val >> 8) & 0xFF;
}
}
}
/* All done: return length of external representation */
_cap_mu_unlock_return(&cap_d->mutex, csz);
}
/*
* Import an external representation to produce an internal rep.
* the internal rep should be liberated with cap_free().
*
* Note, this function assumes that cap_ext has a valid length. That
* is, feeding garbage to this function will likely crash the program.
*/
cap_t cap_copy_int(const void *cap_ext)
{
const struct cap_ext_struct *export =
(const struct cap_ext_struct *) cap_ext;
cap_t cap_d;
int set, blen;
/* Does the external representation make sense? */
if ((export == NULL)
|| memcmp(export->magic, external_magic, CAP_EXT_MAGIC_SIZE)) {
errno = EINVAL;
return NULL;
}
/* Obtain a new internal capability set */
if (!(cap_d = cap_init()))
return NULL;
blen = export->length_of_capset;
for (set=0; set<NUMBER_OF_CAP_SETS; ++set) {
unsigned blk;
int bno = 0;
for (blk=0; blk<(CAP_SET_SIZE/sizeof(__u32)); ++blk) {
__u32 val = 0;
if (bno != blen)
val = export->bytes[bno++][set];
if (bno != blen)
val |= export->bytes[bno++][set] << 8;
if (bno != blen)
val |= export->bytes[bno++][set] << 16;
if (bno != blen)
val |= export->bytes[bno++][set] << 24;
cap_d->u[blk].flat[set] = val;
}
}
/* all done */
return cap_d;
}
/*
* This function is the same as cap_copy_int() although it requires an
* extra argument that is the length of the cap_ext data. Before
* running cap_copy_int() the function validates that length is
* consistent with the stated length. It returns NULL on error.
*/
cap_t cap_copy_int_check(const void *cap_ext, ssize_t length)
{
const struct cap_ext_struct *export =
(const struct cap_ext_struct *) cap_ext;
if (length < 1+CAP_EXT_MAGIC_SIZE) {
errno = EINVAL;
return NULL;
}
if (length < 1+CAP_EXT_MAGIC_SIZE + export->length_of_capset * NUMBER_OF_CAP_SETS) {
errno = EINVAL;
return NULL;
}
return cap_copy_int(cap_ext);
}