mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00
712 lines
18 KiB
C
712 lines
18 KiB
C
/*
|
|
* Copyright (C) 1998-2005 Novell/SUSE
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, version 2 of the
|
|
* License.
|
|
*
|
|
* AppArmor userspace policy interface
|
|
*/
|
|
|
|
#include <asm/unaligned.h>
|
|
|
|
#include "apparmor.h"
|
|
#include "inline.h"
|
|
#include "module_interface.h"
|
|
#include "aamatch/match.h"
|
|
|
|
/* sd_code defined in module_interface.h */
|
|
|
|
const int sdcode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 };
|
|
|
|
struct sd_taskreplace_data {
|
|
struct sdprofile *old_profile;
|
|
struct sdprofile *new_profile;
|
|
};
|
|
|
|
/* inlines must be forward of there use in newer version of gcc,
|
|
just forward declaring with a prototype won't work anymore */
|
|
|
|
static inline void free_sd_entry(struct sd_entry *entry)
|
|
{
|
|
if (entry) {
|
|
kfree(entry->filename);
|
|
sdmatch_free(entry->extradata);
|
|
kfree(entry);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* alloc_sd_entry - create new empty sd_entry
|
|
*
|
|
* This routine allocates, initializes, and returns a new subdomain
|
|
* file entry structure. Structure is zeroed. Returns new structure on
|
|
* success, NULL on failure.
|
|
*/
|
|
static inline struct sd_entry *alloc_sd_entry(void)
|
|
{
|
|
struct sd_entry *entry;
|
|
|
|
SD_DEBUG("%s\n", __FUNCTION__);
|
|
entry = kmalloc(sizeof(struct sd_entry), GFP_KERNEL);
|
|
if (entry) {
|
|
int i;
|
|
memset(entry, 0, sizeof(struct sd_entry));
|
|
INIT_LIST_HEAD(&entry->list);
|
|
for (i = 0; i <= POS_SD_FILE_MAX; i++) {
|
|
INIT_LIST_HEAD(&entry->listp[i]);
|
|
}
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* free_sdprofile - free sdprofile structure
|
|
*/
|
|
void free_sdprofile(struct sdprofile *profile)
|
|
{
|
|
struct sd_entry *sdent, *tmp;
|
|
struct sdprofile *p, *ptmp;
|
|
|
|
SD_DEBUG("%s(%p)\n", __FUNCTION__, profile);
|
|
|
|
if (!profile)
|
|
return;
|
|
|
|
/* profile is still on global profile list -- invalid */
|
|
if (!list_empty(&profile->list)) {
|
|
SD_ERROR("%s: internal error, "
|
|
"profile '%s' still on global list\n",
|
|
__FUNCTION__,
|
|
profile->name);
|
|
BUG();
|
|
}
|
|
|
|
list_for_each_entry_safe(sdent, tmp, &profile->file_entry, list) {
|
|
if (sdent->filename)
|
|
SD_DEBUG("freeing sd_entry: %p %s\n",
|
|
sdent->filename, sdent->filename);
|
|
list_del_init(&sdent->list);
|
|
free_sd_entry(sdent);
|
|
}
|
|
|
|
list_for_each_entry_safe(p, ptmp, &profile->sub, list) {
|
|
list_del_init(&p->list);
|
|
put_sdprofile(p);
|
|
}
|
|
|
|
if (profile->name) {
|
|
SD_DEBUG("%s: %s\n", __FUNCTION__, profile->name);
|
|
kfree(profile->name);
|
|
}
|
|
|
|
kfree(profile);
|
|
}
|
|
|
|
/** task_remove
|
|
*
|
|
* remove profile in a task's subdomain leaving the task unconfined
|
|
*
|
|
* @sd: task's subdomain
|
|
*/
|
|
static inline void task_remove(struct subdomain *sd)
|
|
{
|
|
/* write_lock(&sd_lock) held here */
|
|
SD_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n",
|
|
__FUNCTION__,
|
|
sd->task->comm,
|
|
sd->task->pid,
|
|
sd->profile->name,
|
|
sd->active->name);
|
|
|
|
sd_switch_unconfined(sd);
|
|
}
|
|
|
|
/** taskremove_iter
|
|
*
|
|
* Iterate over all subdomains.
|
|
*
|
|
* If any matches old_profile, then call task_remove to remove it.
|
|
* This leaves the task (subdomain) unconfined.
|
|
*/
|
|
static int taskremove_iter(struct subdomain *sd, void *cookie)
|
|
{
|
|
struct sdprofile *old_profile = (struct sdprofile *)cookie;
|
|
unsigned long flags;
|
|
|
|
write_lock_irqsave(&sd_lock, flags);
|
|
|
|
if (__sd_is_confined(sd) && sd->profile == old_profile)
|
|
task_remove(sd);
|
|
|
|
write_unlock_irqrestore(&sd_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** task_replace
|
|
*
|
|
* replace profile in a task's subdomain with newly loaded profile
|
|
*
|
|
* @sd: task's subdomain
|
|
* @new: old profile
|
|
*/
|
|
static inline void task_replace(struct subdomain *sd, struct sdprofile *new)
|
|
{
|
|
struct sdprofile *nactive = NULL;
|
|
|
|
SD_DEBUG("%s: replacing profile for task %s(%d) "
|
|
"profile=%s (%p) active=%s (%p)\n",
|
|
__FUNCTION__,
|
|
sd->task->comm, sd->task->pid,
|
|
sd->profile->name, sd->profile,
|
|
sd->active->name, sd->active);
|
|
|
|
if (sd->profile == sd->active)
|
|
nactive = get_sdprofile(new);
|
|
else if (sd->active) {
|
|
/* old in hat, new profile has hats */
|
|
nactive = __sd_find_profile(sd->active->name, &new->sub);
|
|
|
|
if (!nactive) {
|
|
if (new->flags.complain)
|
|
nactive = get_sdprofile(null_complain_profile);
|
|
else
|
|
nactive = get_sdprofile(null_profile);
|
|
}
|
|
}
|
|
sd_switch(sd, new, nactive);
|
|
|
|
put_sdprofile(nactive);
|
|
}
|
|
|
|
/** taskreplace_iter
|
|
*
|
|
* Iterate over all subdomains.
|
|
*
|
|
* If any matches old_profile, then call task_replace to replace with
|
|
* new_profile
|
|
*/
|
|
static int taskreplace_iter(struct subdomain *sd, void *cookie)
|
|
{
|
|
struct sd_taskreplace_data *data = (struct sd_taskreplace_data *)cookie;
|
|
unsigned long flags;
|
|
|
|
write_lock_irqsave(&sd_lock, flags);
|
|
|
|
if (__sd_is_confined(sd) && sd->profile == data->old_profile)
|
|
task_replace(sd, data->new_profile);
|
|
|
|
write_unlock_irqrestore(&sd_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int sd_inbounds(struct sd_ext *e, size_t size)
|
|
{
|
|
return (e->pos + size <= e->end);
|
|
}
|
|
|
|
/**
|
|
* sdconvert - for codes that have a trailing value, convert that value
|
|
* and put it in dest.
|
|
* if a code does not have a trailing value nop
|
|
* @code: type code
|
|
* @dest: pointer to object to receive the converted value
|
|
* @src: pointer to value to convert
|
|
*/
|
|
static void sdconvert(enum sd_code code, void *dest, void *src)
|
|
{
|
|
switch (code) {
|
|
case SD_U8:
|
|
*(u8 *)dest = *(u8 *) src;
|
|
break;
|
|
case SD_U16:
|
|
case SD_NAME:
|
|
case SD_DYN_STRING:
|
|
*(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src));
|
|
break;
|
|
case SD_U32:
|
|
case SD_STATIC_BLOB:
|
|
*(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src));
|
|
break;
|
|
case SD_U64:
|
|
*(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src));
|
|
break;
|
|
default:
|
|
/* nop - all other type codes do not have a trailing value */
|
|
;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* sd_is_X - check if the next element is of type X and if it is within
|
|
* bounds. If it is put the associated value in data.
|
|
* @e: extent information
|
|
* @code: type code
|
|
* @data: object located at @e->pos (of type @code) is written into @data
|
|
* if @data is non-null. if data is null it means skip this
|
|
* entry
|
|
* return the size of bytes associated with the returned data
|
|
* for complex object like blob and string a pointer to the allocated
|
|
* data is returned in data, but the size of the blob or string is
|
|
* returned.
|
|
*/
|
|
static u32 sd_is_X(struct sd_ext *e, enum sd_code code, void *data)
|
|
{
|
|
void *pos = e->pos;
|
|
int ret = 0;
|
|
if (!sd_inbounds(e, SD_CODE_BYTE + sdcode_datasize[code]))
|
|
goto fail;
|
|
if (code != *(u8 *)e->pos)
|
|
goto out;
|
|
e->pos += SD_CODE_BYTE;
|
|
if (code == SD_NAME) {
|
|
u16 size;
|
|
/* name codes are followed by X bytes */
|
|
size = le16_to_cpu(get_unaligned((u16 *)e->pos));
|
|
if (!sd_inbounds(e, (size_t) size))
|
|
goto fail;
|
|
if (data)
|
|
*(u16 *)data = size;
|
|
e->pos += sdcode_datasize[code];
|
|
ret = 1 + sdcode_datasize[code];
|
|
} else if (code == SD_DYN_STRING) {
|
|
u16 size;
|
|
char *str;
|
|
/* strings codes are followed by X bytes */
|
|
size = le16_to_cpu(get_unaligned((u16 *)e->pos));
|
|
e->pos += sdcode_datasize[code];
|
|
if (!sd_inbounds(e, (size_t) size))
|
|
goto fail;
|
|
if (data) {
|
|
* (char **)data = NULL;
|
|
str = kmalloc(size, GFP_KERNEL);
|
|
if (!str)
|
|
goto fail;
|
|
memcpy(str, e->pos, (size_t) size);
|
|
str[size-1] = '\0';
|
|
* (char **)data = str;
|
|
}
|
|
e->pos += size;
|
|
ret = size;
|
|
} else if (code == SD_STATIC_BLOB) {
|
|
u32 size;
|
|
/* blobs are followed by X bytes, that can be 2^32 */
|
|
size = le32_to_cpu(get_unaligned((u32 *)e->pos));
|
|
e->pos += sdcode_datasize[code];
|
|
if (!sd_inbounds(e, (size_t) size))
|
|
goto fail;
|
|
if (data)
|
|
memcpy(data, e->pos, (size_t) size);
|
|
e->pos += size;
|
|
ret = size;
|
|
} else {
|
|
if (data)
|
|
sdconvert(code, data, e->pos);
|
|
e->pos += sdcode_datasize[code];
|
|
ret = 1 + sdcode_datasize[code];
|
|
}
|
|
out:
|
|
return ret;
|
|
fail:
|
|
e->pos = pos;
|
|
return 0;
|
|
}
|
|
|
|
/* sd_is_nameX - check is the next element is X, and its tag is name.
|
|
* if the code matches and name (if specified) matches then the packed data
|
|
* is unpacked into *data. (Note for strings this is the size, and the next
|
|
* data in the stream is the string data)
|
|
* returns 0 if either match failes
|
|
*/
|
|
static int sd_is_nameX(struct sd_ext *e, enum sd_code code, void *data,
|
|
const char *name)
|
|
{
|
|
void *pos = e->pos;
|
|
u16 size;
|
|
u32 ret;
|
|
/* check for presence of a tagname, and if present name size
|
|
* SD_NAME tag value is a u16 */
|
|
if (sd_is_X(e, SD_NAME, &size)) {
|
|
/* if a name is specified it must match. otherwise skip tag */
|
|
if (name && ((strlen(name) != size-1) ||
|
|
strncmp(name, (char *)e->pos, (size_t)size-1)))
|
|
goto fail;
|
|
e->pos += size;
|
|
}
|
|
/* now check if data actually matches */
|
|
ret = sd_is_X(e, code, data);
|
|
if (!ret)
|
|
goto fail;
|
|
return ret;
|
|
|
|
fail:
|
|
e->pos = pos;
|
|
return 0;
|
|
}
|
|
|
|
/* macro to wrap error case to make a block of reads look nicer */
|
|
#define SD_READ_X(E, C, D, N) \
|
|
do { \
|
|
u32 __ret; \
|
|
__ret = sd_is_nameX((E), (C), (D), (N)); \
|
|
if (!__ret) \
|
|
goto fail; \
|
|
} while (0)
|
|
|
|
/**
|
|
* sd_activate_net_entry - ignores/skips net entries if the they are present
|
|
* in the data stream.
|
|
* @e: extent information
|
|
*/
|
|
static inline int sd_activate_net_entry(struct sd_ext *e)
|
|
{
|
|
SD_READ_X(e, SD_STRUCT, NULL, "ne");
|
|
SD_READ_X(e, SD_U32, NULL, NULL);
|
|
SD_READ_X(e, SD_U32, NULL, NULL);
|
|
SD_READ_X(e, SD_U32, NULL, NULL);
|
|
SD_READ_X(e, SD_U16, NULL, NULL);
|
|
SD_READ_X(e, SD_U16, NULL, NULL);
|
|
SD_READ_X(e, SD_U32, NULL, NULL);
|
|
SD_READ_X(e, SD_U32, NULL, NULL);
|
|
SD_READ_X(e, SD_U16, NULL, NULL);
|
|
SD_READ_X(e, SD_U16, NULL, NULL);
|
|
/* interface name is optional so just ignore return code */
|
|
sd_is_nameX(e, SD_DYN_STRING, NULL, NULL);
|
|
SD_READ_X(e, SD_STRUCTEND, NULL, NULL);
|
|
|
|
return 1;
|
|
fail:
|
|
return 0;
|
|
}
|
|
|
|
static inline struct sd_entry *sd_activate_file_entry(struct sd_ext *e)
|
|
{
|
|
struct sd_entry *entry = NULL;
|
|
|
|
if (!(entry = alloc_sd_entry()))
|
|
goto fail;
|
|
|
|
SD_READ_X(e, SD_STRUCT, NULL, "fe");
|
|
SD_READ_X(e, SD_DYN_STRING, &entry->filename, NULL);
|
|
SD_READ_X(e, SD_U32, &entry->mode, "file.mode");
|
|
SD_READ_X(e, SD_U32, &entry->entry_type, "file.pattern_type");
|
|
|
|
entry->extradata = sdmatch_alloc(entry->entry_type);
|
|
if (IS_ERR(entry->extradata)) {
|
|
entry->extradata = NULL;
|
|
goto fail;
|
|
}
|
|
|
|
if (entry->extradata &&
|
|
sdmatch_serialize(entry->extradata, e, sd_is_nameX) != 0) {
|
|
goto fail;
|
|
}
|
|
SD_READ_X(e, SD_STRUCTEND, NULL, NULL);
|
|
|
|
switch (entry->entry_type) {
|
|
case sd_entry_literal:
|
|
SD_DEBUG("%s: %s [no pattern] mode=0x%x\n",
|
|
__FUNCTION__,
|
|
entry->filename,
|
|
entry->mode);
|
|
break;
|
|
case sd_entry_tailglob:
|
|
SD_DEBUG("%s: %s [tailglob] mode=0x%x\n",
|
|
__FUNCTION__,
|
|
entry->filename,
|
|
entry->mode);
|
|
break;
|
|
case sd_entry_pattern:
|
|
SD_DEBUG("%s: %s mode=0x%x\n",
|
|
__FUNCTION__,
|
|
entry->filename,
|
|
entry->mode);
|
|
break;
|
|
default:
|
|
SD_WARN("%s: INVALID entry_type %d\n",
|
|
__FUNCTION__,
|
|
(int)entry->entry_type);
|
|
goto fail;
|
|
}
|
|
|
|
return entry;
|
|
|
|
fail:
|
|
sdmatch_free(entry->extradata);
|
|
free_sd_entry(entry);
|
|
return NULL;
|
|
}
|
|
|
|
static inline int check_rule_and_add(struct sd_entry *file_entry,
|
|
struct sdprofile *profile,
|
|
const char **message)
|
|
{
|
|
/* verify consistency of x, px, ix, ux for entry against
|
|
possible duplicates for this entry */
|
|
int mode = SD_EXEC_MODIFIER_MASK(file_entry->mode);
|
|
int i;
|
|
|
|
if (mode && !(SD_MAY_EXEC & file_entry->mode)) {
|
|
*message = "inconsistent rule, x modifiers without x";
|
|
goto out;
|
|
}
|
|
|
|
/* check that only 1 of the modifiers is set */
|
|
if (mode && (mode & (mode - 1))) {
|
|
*message = "inconsistent rule, multiple x modifiers";
|
|
goto out;
|
|
}
|
|
|
|
/* ix -> m (required so that target exec binary may map itself) */
|
|
if (mode & SD_EXEC_INHERIT)
|
|
file_entry->mode |= SD_EXEC_MMAP;
|
|
|
|
list_add(&file_entry->list, &profile->file_entry);
|
|
profile->num_file_entries++;
|
|
|
|
mode = file_entry->mode;
|
|
|
|
/* Handle partitioned lists
|
|
* Chain entries onto sublists based on individual
|
|
* permission bits. This allows more rapid searching.
|
|
*/
|
|
for (i = 0; i <= POS_SD_FILE_MAX; i++) {
|
|
if (mode & (1 << i))
|
|
/* profile->file_entryp[i] initially set to
|
|
* NULL in alloc_sdprofile() */
|
|
list_add(&file_entry->listp[i],
|
|
&profile->file_entryp[i]);
|
|
}
|
|
|
|
return 1;
|
|
|
|
out:
|
|
free_sd_entry(file_entry);
|
|
return 0;
|
|
}
|
|
|
|
#define SD_ENTRY_LIST(NAME) \
|
|
do { \
|
|
if (sd_is_nameX(e, SD_LIST, NULL, (NAME))) { \
|
|
rulename = ""; \
|
|
error_string = "Invalid file entry"; \
|
|
while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { \
|
|
struct sd_entry *file_entry; \
|
|
file_entry = sd_activate_file_entry(e); \
|
|
if (!file_entry) \
|
|
goto fail; \
|
|
if (!check_rule_and_add(file_entry, profile, \
|
|
&error_string)) { \
|
|
rulename = file_entry->filename; \
|
|
goto fail; \
|
|
} \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
struct sdprofile *sd_activate_profile(struct sd_ext *e, ssize_t *error)
|
|
{
|
|
struct sdprofile *profile = NULL;
|
|
const char *rulename = "";
|
|
const char *error_string = "Invalid Profile";
|
|
|
|
*error = -EPROTO;
|
|
|
|
profile = alloc_sdprofile();
|
|
if (!profile) {
|
|
error_string = "Could not allocate profile";
|
|
*error = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
/* check that we have the right struct being passed */
|
|
SD_READ_X(e, SD_STRUCT, NULL, "profile");
|
|
SD_READ_X(e, SD_DYN_STRING, &profile->name, NULL);
|
|
|
|
error_string = "Invalid flags";
|
|
/* per profile debug flags (debug, complain, audit) */
|
|
SD_READ_X(e, SD_STRUCT, NULL, "flags");
|
|
SD_READ_X(e, SD_U32, &(profile->flags.debug), "profile.flags.debug");
|
|
SD_READ_X(e, SD_U32, &(profile->flags.complain),
|
|
"profile.flags.complain");
|
|
SD_READ_X(e, SD_U32, &(profile->flags.audit), "profile.flags.audit");
|
|
SD_READ_X(e, SD_STRUCTEND, NULL, NULL);
|
|
|
|
error_string = "Invalid capabilities";
|
|
SD_READ_X(e, SD_U32, &(profile->capabilities), "profile.capabilities");
|
|
|
|
/* get the file entries. */
|
|
SD_ENTRY_LIST("pgent"); /* pcre rules */
|
|
SD_ENTRY_LIST("sgent"); /* simple globs */
|
|
SD_ENTRY_LIST("fent"); /* regular file entries */
|
|
|
|
/* get the net entries */
|
|
if (sd_is_nameX(e, SD_LIST, NULL, "net")) {
|
|
error_string = "Invalid net entry";
|
|
while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) {
|
|
if (!sd_activate_net_entry(e))
|
|
goto fail;
|
|
}
|
|
}
|
|
rulename = "";
|
|
|
|
/* get subprofiles */
|
|
if (sd_is_nameX(e, SD_LIST, NULL, "hats")) {
|
|
error_string = "Invalid profile hat";
|
|
while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) {
|
|
struct sdprofile *subprofile;
|
|
subprofile = sd_activate_profile(e, error);
|
|
if (!subprofile)
|
|
goto fail;
|
|
get_sdprofile(subprofile);
|
|
list_add(&subprofile->list, &profile->sub);
|
|
}
|
|
}
|
|
|
|
error_string = "Invalid end of profile";
|
|
SD_READ_X(e, SD_STRUCTEND, NULL, NULL);
|
|
|
|
return profile;
|
|
|
|
fail:
|
|
SD_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename,
|
|
error_string, profile && profile->name ? profile->name
|
|
: "unknown");
|
|
|
|
if (profile) {
|
|
free_sdprofile(profile);
|
|
profile = NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void *sd_activate_top_profile(struct sd_ext *e, ssize_t *error)
|
|
{
|
|
/* get the interface version */
|
|
if (!sd_is_nameX(e, SD_U32, &e->version, "version")) {
|
|
SD_WARN("%s: version missing\n", INTERFACE_ID);
|
|
*error = -EPROTONOSUPPORT;
|
|
goto out;
|
|
}
|
|
|
|
/* check that the interface version is currently supported */
|
|
if (e->version != 2) {
|
|
SD_WARN("%s: unsupported interface version (%d)\n",
|
|
INTERFACE_ID, e->version);
|
|
*error = -EPROTONOSUPPORT;
|
|
goto out;
|
|
}
|
|
|
|
return sd_activate_profile(e, error);
|
|
out:
|
|
return NULL;
|
|
}
|
|
|
|
ssize_t sd_file_prof_add(void *data, size_t size)
|
|
{
|
|
struct sdprofile *profile = NULL;
|
|
|
|
struct sd_ext e = { data, data + size, data };
|
|
ssize_t error;
|
|
|
|
profile = sd_activate_top_profile(&e, &error);
|
|
if (!profile) {
|
|
SD_DEBUG("couldn't activate profile\n");
|
|
return error;
|
|
}
|
|
|
|
if (!sd_profilelist_add(profile)) {
|
|
SD_WARN("trying to add profile (%s) that already exists.\n",
|
|
profile->name);
|
|
free_sdprofile(profile);
|
|
return -EEXIST;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
ssize_t sd_file_prof_repl(void *udata, size_t size)
|
|
{
|
|
struct sd_taskreplace_data data;
|
|
struct sd_ext e = { udata, udata + size, udata };
|
|
ssize_t error;
|
|
|
|
data.new_profile = sd_activate_top_profile(&e, &error);
|
|
if (!data.new_profile) {
|
|
SD_DEBUG("couldn't activate profile\n");
|
|
return error;
|
|
}
|
|
/* Grab reference to close race window (see comment below) */
|
|
get_sdprofile(data.new_profile);
|
|
|
|
/* Replace the profile on the global profile list.
|
|
* This list is used by all new exec's to find the correct profile.
|
|
* If there was a previous profile, it is returned, else NULL.
|
|
*
|
|
* N.B sd_profilelist_replace does not drop the refcnt on
|
|
* old_profile when removing it from the global list, otherwise it
|
|
* could reach zero and be automatically free'd. We nust manually
|
|
* drop it at the end of this function when we are finished with it.
|
|
*/
|
|
data.old_profile = sd_profilelist_replace(data.new_profile);
|
|
|
|
/* RACE window here.
|
|
* At this point another task could preempt us trying to replace
|
|
* the SAME profile. If it makes it to this point, it has removed
|
|
* the original tasks new_profile from the global list and holds a
|
|
* reference of 1 to it in it's old_profile. If the new task
|
|
* reaches the end of the function it will put old_profile causing
|
|
* the profile to be deleted.
|
|
* When the original task is rescheduled it will continue calling
|
|
* sd_subdomainlist_iterate relabelling tasks with a profile
|
|
* which points to free'd memory.
|
|
*/
|
|
|
|
/* If there was an old profile, find all currently executing tasks
|
|
* using this profile and replace the old profile with the new.
|
|
*/
|
|
if (data.old_profile) {
|
|
SD_DEBUG("%s: try to replace profile (%p)%s\n",
|
|
__FUNCTION__,
|
|
data.old_profile,
|
|
data.old_profile->name);
|
|
|
|
sd_subdomainlist_iterate(taskreplace_iter, (void *)&data);
|
|
|
|
/* it's off global list, and we are done replacing */
|
|
put_sdprofile(data.old_profile);
|
|
}
|
|
|
|
/* Free reference obtained above */
|
|
put_sdprofile(data.new_profile);
|
|
|
|
return size;
|
|
}
|
|
|
|
ssize_t sd_file_prof_remove(const char *name, size_t size)
|
|
{
|
|
struct sdprofile *old_profile;
|
|
|
|
/* if the old profile exists it will be removed from the list and
|
|
* a reference is returned.
|
|
*/
|
|
old_profile = sd_profilelist_remove(name);
|
|
|
|
if (old_profile) {
|
|
/* remove profile from any tasks using it */
|
|
sd_subdomainlist_iterate(taskremove_iter, (void *)old_profile);
|
|
|
|
/* drop reference obtained by sd_profilelist_remove */
|
|
put_sdprofile(old_profile);
|
|
} else {
|
|
SD_WARN("%s: trying to remove profile (%s) that "
|
|
"doesn't exist - skipping.\n", __FUNCTION__, name);
|
|
return -ENOENT;
|
|
}
|
|
|
|
return size;
|
|
}
|