Re: [PATCH 4/6] security_dac: Implement transaction APIs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 01/07/2017 03:05 PM, John Ferlan wrote:
> 
> 
> On 12/19/2016 10:57 AM, Michal Privoznik wrote:
>> Signed-off-by: Michal Privoznik <mprivozn@xxxxxxxxxx>
>> ---
>>  src/security/security_dac.c | 193 ++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 193 insertions(+)
>>
> 
> Might have been nice to add some basic/sparse documentation over return
> values...
> 
> BTW: I'm stopping here since I have some functionality concerns that
> would probably be replicated in _selinux and the qemu implementation.
> 
>> diff --git a/src/security/security_dac.c b/src/security/security_dac.c
>> index b6f75fe1d..0208c1751 100644
>> --- a/src/security/security_dac.c
>> +++ b/src/security/security_dac.c
>> @@ -68,6 +68,117 @@ struct _virSecurityDACCallbackData {
>>      virSecurityLabelDefPtr secdef;
>>  };
>>  
>> +typedef struct _virSecurityDACChownItem virSecurityDACChownItem;
>> +typedef virSecurityDACChownItem *virSecurityDACChownItemPtr;
>> +struct _virSecurityDACChownItem {
>> +    const char *path;
>> +    const virStorageSource *src;
>> +    uid_t uid;
>> +    gid_t gid;
>> +};
>> +
>> +typedef struct _virSecurityDACChownList virSecurityDACChownList;
>> +typedef virSecurityDACChownList *virSecurityDACChownListPtr;
>> +struct _virSecurityDACChownList {
>> +    virSecurityDACDataPtr priv;
>> +    virSecurityDACChownItemPtr *items;
>> +    size_t nItems;
>> +};
>> +
>> +
>> +virThreadLocal chownList;
>> +
>> +static int
>> +virSecurityDACChownListAppend(virSecurityDACChownListPtr list,
>> +                              const char *path,
>> +                              const virStorageSource *src,
>> +                              uid_t uid,
>> +                              gid_t gid)
>> +{
>> +    virSecurityDACChownItemPtr item;
>> +
>> +    if (VIR_ALLOC(item) < 0)
>> +        return -1;
>> +
>> +    item->path = path;
>> +    item->src = src;
>> +    item->uid = uid;
>> +    item->gid = gid;
>> +
>> +    if (VIR_APPEND_ELEMENT(list->items, list->nItems, item) < 0) {
>> +        VIR_FREE(item);
>> +        return -1;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static void
>> +virSecurityDACChownListFree(void *opaque)
>> +{
>> +    virSecurityDACChownListPtr list = opaque;
>> +    size_t i;
>> +
>> +    if (!list)
>> +        return;
>> +
>> +    for (i = 0; i < list->nItems; i++)
>> +        VIR_FREE(list->items[i]);
>> +    VIR_FREE(list);
>> +}
>> +
>> +
> 
> This is one example where documenting return values would help... Since
> whether 'list' is NULL or not is important from the callers viewpoint.
> 
> I also note the caller will do something different when ret > 0, but I
> see no way for that to happen...
> 
>> +static int
>> +virSecurityDACTransactionAppend(const char *path,
>> +                                const virStorageSource *src,
>> +                                uid_t uid,
>> +                                gid_t gid)
>> +{
>> +    virSecurityDACChownListPtr list;
>> +    int ret = -1;
>> +
>> +    list = virThreadLocalGet(&chownList);
>> +    if (!list)
>> +        return 0;
>> +
>> +    if (virSecurityDACChownListAppend(list, path, src, uid, gid) < 0)
>> +        goto cleanup;
>> +
>> +    ret = 0;
> 
> Should this be ret = 1 so the caller "knows" something was put on list?
> and thus returns 0 rather than running the rest of the code.

Oh right. I AM a giddy goat.

> 
>> + cleanup:
>> +    return ret;
>> +}
>> +
>> +
>> +static int virSecurityDACSetOwnershipInternal(const virSecurityDACData *priv,
>> +                                              const virStorageSource *src,
>> +                                              const char *path,
>> +                                              uid_t uid,
>> +                                              gid_t gid);
>> +
> 
> Some comments here would be nice.
> 
>> +static int
>> +virSecurityDACTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
>> +                             void *opaque)
>> +{
>> +    virSecurityDACChownListPtr list = opaque;
>> +    size_t i;
>> +
>> +    ignore_value(virThreadLocalSet(&chownList, NULL));
> 
> Not that it should fail, but part of me wonders if a VIR_DEBUG should
> issued...
> 
> This point in the process is the key step in the whole process - it's
> from this point on that we do the "real" work.
> 
> Still after a bit of reading on and thinking - should this clearing be
> done in Commit instead prior to calling Run

No. For couple of reasons:
1) this function is run from a separate process so unsetting this has no
affect on the parent process (libvirtd).
2) We have to unset the thread local otherwise
virSecurityDACSetOwnershipInternal() would still see the thread local
variable and think there's a transaction set up. Thus instead of doing
real chown() it would just append items to the list.

But okay, I will put this into the comment. And also check for retval of
virThreadLocalSet().

> 
>> +    for (i = 0; i < list->nItems; i++) {
>> +        virSecurityDACChownItemPtr item = list->items[i];
>> +
>> +        if (virSecurityDACSetOwnershipInternal(list->priv,
>> +                                               item->src,
>> +                                               item->path,
>> +                                               item->uid,
>> +                                               item->gid) < 0)
>> +            return -1;
> 
> If we have a failure, then should we rollback everything done to this
> point and of course how would we accomplish that?
> 
> The problem being list is available only here and in Commit so how do we
> really know how much has been *really* committed and of course does that
> matter if our caller can "rollback" for us.
> 
> If I read things right, the caller would generate the same "list" in
> rollback fashion and rollback everything rather than perhaps being
> smarter and rolling back only what was changed. Although I suppose
> rolling back everything would/could cause failure at the same point as
> Commit.

Exactly. BTW: current code has the same limitation. If an error occurs
in SetSecurityAllLabel() (called from qemuProcessLaunch for instance),
all callers eventually call RestoreSecurityAllLabel() (called rom
qemuProcessStop).

> 
> Still we have no real way of restoring/rolling back from the point of
> failure unless commit and/or this function returns that mechanism. It
> would seem the rollback of RestoreAll labels would/could be overkill.

Agreed.

> 
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +
>>  /* returns -1 on error, 0 on success */
>>  int
>>  virSecurityDACSetUserAndGroup(virSecurityManagerPtr mgr,
>> @@ -238,6 +349,13 @@ virSecurityDACProbe(const char *virtDriver ATTRIBUTE_UNUSED)
>>  static int
>>  virSecurityDACOpen(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
>>  {
>> +    if (virThreadLocalInit(&chownList,
>> +                           virSecurityDACChownListFree) < 0) {
>> +        virReportSystemError(errno, "%s",
>> +                             _("Unable to initialize thread local variable"));
>> +        return -1;
>> +    }
>> +
>>      return 0;
>>  }
>>  
>> @@ -278,6 +396,69 @@ virSecurityDACPreFork(virSecurityManagerPtr mgr)
>>      return 0;
>>  }
>>  
>> +static int
>> +virSecurityDACTransactionStart(virSecurityManagerPtr mgr)
>> +{
>> +    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
>> +    virSecurityDACChownListPtr list;
>> +
>> +    list = virThreadLocalGet(&chownList);
>> +    if (list) {
>> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> +                       _("Another relabel transaction is already started"));
>> +        return -1;
>> +    }
>> +
>> +    if (VIR_ALLOC(list) < 0)
>> +        return -1;
>> +
>> +    list->priv = priv;
>> +
>> +    if (virThreadLocalSet(&chownList, list) < 0) {
>> +        virReportSystemError(errno, "%s",
>> +                             _("Unable to set thread local variable"));
>> +        VIR_FREE(list);
>> +        return -1;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int
>> +virSecurityDACTransactionCommit(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
>> +                                pid_t pid)
>> +{
>> +    virSecurityDACChownListPtr list;
>> +    int ret = 0;
>> +
>> +    list = virThreadLocalGet(&chownList);
>> +    if (!list)
>> +        return 0;
> 
> Wouldn't !list only happen if there was an error and we're calling
> Commit more or less unexpectedly (either after some error that caused
> Abort() to be called or a second time)?  IOW: Should this be an error?

It should if we want callers to be allowed to call TransactionCommit()
only after prior TransactionStart() call. If you look at the very last
patch of mine there's the following pattern:

if (namespaceEnabled)
  transactionStart();

securityDriverCall();

transactionCommit();
transactionAbort();

If the namespace is not enabled both transactionCommit() and
transactionAbort() are no-ops basically. But okay, I can make this
function return an error in this case and thus change pattern to:

if (namespaceEnabled)
  transactionStart();

securityDriverCall();

if (namespaceEnabled)
  transactionCommit();
transactionAbort();

I just thought that my current approach saves couple of LOC.

> 
>> +
>> +    if (virProcessRunInMountNamespace(pid,
>> +                                      virSecurityDACTransactionRun,
>> +                                      list) < 0)
>> +        ret = -1;
>> +
>> +    ignore_value(virThreadLocalSet(&chownList, NULL));
> 
> Not that it should fail, but part of me wonders if a VIR_DEBUG should
> issued...  Secondary to that - should setting to NULL be done before
> RunInMountNamespace and not in TransactionRun?

Ah, good point. If I do this before calling RunInMountNamespace() I
don't have to do it there either.

> 
> Also for a rollback/error type handling, rather than passing list -
> should we pass a structure that includes list and a count of successful
> commits.  That could be compared against list.nItems and on error
> returned to the caller to have some chance at successful rollback.
> 
>> +    virSecurityDACChownListFree(list);
>> +    return ret;
>> +}
>> +
>> +static void
>> +virSecurityDACTransactionAbort(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
>> +{
>> +    virSecurityDACChownListPtr list;
>> +
>> +    list = virThreadLocalGet(&chownList);
>> +    if (!list)
>> +        return;
>> +
>> +    ignore_value(virThreadLocalSet(&chownList, NULL));
> 
> Not that it should fail, but part of me wonders if a VIR_DEBUG should
> issued...
> 

Yep. Will change that here and in all the other lines too.

>> +    virSecurityDACChownListFree(list);
>> +}
>> +
>> +
>>  static int
>>  virSecurityDACSetOwnershipInternal(const virSecurityDACData *priv,
>>                                     const virStorageSource *src,
>> @@ -287,6 +468,14 @@ virSecurityDACSetOwnershipInternal(const virSecurityDACData *priv,
>>  {
>>      int rc;
>>  
>> +    /* Be aware that this function might run in a separate process.
>> +     * Therefore, any driver state changes would be thrown away. */
>> +
>> +    if ((rc = virSecurityDACTransactionAppend(path, src, uid, gid)) < 0)
>> +        return -1;
>> +    else if (rc > 0)
>> +        return 0;
>> +
> 
> If we call virSecurityDACChownListAppend and ret = 0, then we still fall
> into the code below which from my read of what this code is going to do
> may not be expected...

Yeah, for some reason my code was prepared for the behaviour you
describe in the review, but my code does not implement it in the end.

Michal

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list



[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]
  Powered by Linux