Skip to content

ENT-10961, CFE-1840: Files promise can now modify immutable bit in file system attributes #5694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions cf-agent/verify_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
#include <known_dirs.h>
#include <evalfunction.h>
#include <changes_chroot.h> /* PrepareChangesChroot(), RecordFileChangedInChroot() */
#include <fsattrs.h>

static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp);
static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp);
Expand Down Expand Up @@ -303,6 +304,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
CfLock thislock;
int exists;
bool link = false;
bool is_immutable = false;

Attributes attr = GetFilesAttributes(ctx, pp);

Expand Down Expand Up @@ -351,6 +353,82 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
changes_path = chrooted_path;
}

FSAttrsResult res = FSAttrsGetImmutableFlag(path, &is_immutable);
switch (res)
{
case FS_ATTRS_SUCCESS:
/* Nothing more to do */
break;
case FS_ATTRS_FAILURE:
Log(LOG_LEVEL_VERBOSE, "Failed to get immutable bit for file '%s': %s", changes_path, GetErrorStr());
break;
case FS_ATTRS_NOT_SUPPORTED:
Log(LOG_LEVEL_VERBOSE, "Failed to get immutable bit for file '%s': Operation not supported", changes_path);
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* The file does not exist. Nothing more to do */
break;
}

if (a.havefsattrs && a.fsattrs.haveimmutable)
{
if (a.fsattrs.immutable) /* file should be immutable */
{
if (is_immutable) /* file is already immutable */
{
/* Temporarily remove the immutable flag */
res = FSAttrsUpdateImmutableFlag(changes_path, false);
switch (res)
{
case FS_ATTRS_SUCCESS:
Log(LOG_LEVEL_VERBOSE, "Temporarily cleared immutable bit for file '%s'", changes_path);
break;
case FS_ATTRS_FAILURE:
/* Things still may be fine as long as the agent does not try to mutate the file */
Log(LOG_LEVEL_VERBOSE, "Failed to temporarily clear immutable bit for file '%s': %s", changes_path, GetErrorStr());
break;
case FS_ATTRS_NOT_SUPPORTED:
/* Things still may be fine as long as the agent does not try to mutate the file */
Log(LOG_LEVEL_VERBOSE, "Failed to temporarily clear immutable bit for file '%s': Operation not supported", changes_path);
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* The file does not exist. Nothing more to do */
break;
}
} /* else then file is not immutable, but we will fix that later */
}
else /* file should not be immutable */
{
if (is_immutable) /* file is immutable */
{
/* Remove immutable flag for good */
res = FSAttrsUpdateImmutableFlag(changes_path, false);
switch (res)
{
case FS_ATTRS_SUCCESS:
RecordChange(ctx, pp, &a, "Cleared immutable bit for file '%s'", changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
break;
case FS_ATTRS_FAILURE:
RecordFailure(ctx, pp, &a, "Failed to clear immutable bit for file '%s'", changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
break;
case FS_ATTRS_NOT_SUPPORTED:
/* We will not treat this as a promise failure because this will happen on many platforms. Instead we will log a verbose message. */
Log(LOG_LEVEL_VERBOSE, "Failed to temporarily clear immutable bit for file '%s': Operation not supported", changes_path);
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* Nothing to do */
break;
}
}
else /* file is not immutable */
{
RecordNoChange(ctx, pp, &a, "Immutable bit is not set for file '%s' as promised", changes_path);
}
}
}

if (lstat(changes_path, &oslb) == -1) /* Careful if the object is a link */
{
if ((a.create) || (a.touch))
Expand Down Expand Up @@ -609,6 +687,76 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
}

exit:
; /* A label can only be part of a statement and a declaration is not a
* statement. This is a quirk of standard C grammar. Hence, we put an
* empty statement here. */
bool was_immutable = is_immutable;
is_immutable = false;
res = FSAttrsGetImmutableFlag(path, &is_immutable);
switch (res)
{
case FS_ATTRS_SUCCESS:
/* Nothing to do */
break;
case FS_ATTRS_FAILURE:
Log(LOG_LEVEL_VERBOSE, "Failed to get immutable bit for file '%s': %s", changes_path, GetErrorStr());
break;
case FS_ATTRS_NOT_SUPPORTED:
Log(LOG_LEVEL_VERBOSE, "Failed to get immutable bit for file '%s': Operation not supported", changes_path);
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* Nothing to do */
break;
}

if (a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable) {
if (was_immutable && !is_immutable)
{
/* Reset immutable flag that we cleared before */
res = FSAttrsUpdateImmutableFlag(changes_path, true);
switch (res)
{
case FS_ATTRS_SUCCESS:
Log(LOG_LEVEL_VERBOSE, "Reset immutable bit for file '%s' after temporarily clearing it", changes_path);
break;
case FS_ATTRS_FAILURE:
/* Things still may be fine as long as the agent does not try to mutate the file */
Log(LOG_LEVEL_VERBOSE, "Failed to reset immutable bit for file '%s' after temporarily clearing it: %s", changes_path, GetErrorStr());
break;
case FS_ATTRS_NOT_SUPPORTED:
/* Things still may be fine as long as the agent does not try to mutate the file */
Log(LOG_LEVEL_VERBOSE, "Failed to reset immutable bit for file '%s' after temporarily clearing it: Operation not supported", changes_path);
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* The file must have been removed by the agent. Nothing more to do */
break;
}
}
else
{
/* The file was never immutable, but should be. We'll fix that now */
res = FSAttrsUpdateImmutableFlag(changes_path, true);
switch (res)
{
case FS_ATTRS_SUCCESS:
RecordChange(ctx, pp, &a, "Set immutable bit for file '%s'", changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
break;
case FS_ATTRS_FAILURE:
RecordFailure(ctx, pp, &a, "Failed to set immutable bit for file '%s'", changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
break;
case FS_ATTRS_NOT_SUPPORTED:
/* We will not treat this as a promise failure because this will happen on many platforms. Instead we will log a verbose message. */
Log(LOG_LEVEL_VERBOSE, "Failed set immutable bit for file '%s': Operation not supported", changes_path);
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* The file must have been removed by the agent. Nothing more to do */
break;
}
}
}

free(chrooted_path);
if (AttrHasNoAction(&a))
{
Expand Down
19 changes: 19 additions & 0 deletions libpromises/attributes.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp)
attr.haveselect = PromiseGetConstraintAsBoolean(ctx, "file_select", pp);
attr.haverename = PromiseGetConstraintAsBoolean(ctx, "rename", pp);
attr.havedelete = PromiseGetConstraintAsBoolean(ctx, "delete", pp);
attr.havefsattrs = PromiseBundleOrBodyConstraintExists(ctx, "fsattrs", pp);
attr.content = PromiseGetConstraintAsRval(pp, "content", RVAL_TYPE_SCALAR);
attr.haveperms = PromiseGetConstraintAsBoolean(ctx, "perms", pp);
attr.havechange = PromiseGetConstraintAsBoolean(ctx, "changes", pp);
Expand Down Expand Up @@ -89,6 +90,7 @@ Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp)
attr.perms = GetPermissionConstraints(ctx, pp);
attr.select = GetSelectConstraints(ctx, pp);
attr.delete = GetDeleteConstraints(ctx, pp);
attr.fsattrs = GetFSAttrsConstraints(ctx, pp);
attr.rename = GetRenameConstraints(ctx, pp);
attr.change = GetChangeMgtConstraints(ctx, pp);
attr.copy = GetCopyConstraints(ctx, pp);
Expand Down Expand Up @@ -830,6 +832,23 @@ FileDelete GetDeleteConstraints(const EvalContext *ctx, const Promise *pp)
return f;
}

/*******************************************************************/

FileFSAttrs GetFSAttrsConstraints(const EvalContext *ctx, const Promise *pp)
{
assert(ctx != NULL);
assert(pp != NULL);

FileFSAttrs f =
{
.immutable = PromiseGetConstraintAsBoolean(ctx, "immutable", pp),
.haveimmutable = PromiseBundleOrBodyConstraintExists(ctx, "immutable", pp),
};

return f;
}


/*******************************************************************/

FileRename GetRenameConstraints(const EvalContext *ctx, const Promise *pp)
Expand Down
1 change: 1 addition & 0 deletions libpromises/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ENTERPRISE_FUNC_0ARG_DECLARE(HashMethod, GetBestFileChangeHashMethod);
FileChange GetChangeMgtConstraints(const EvalContext *ctx, const Promise *pp);
FileCopy GetCopyConstraints(const EvalContext *ctx, const Promise *pp);
FileDelete GetDeleteConstraints(const EvalContext *ctx, const Promise *pp);
FileFSAttrs GetFSAttrsConstraints(const EvalContext *ctx, const Promise *pp);
FileLink GetLinkConstraints(const EvalContext *ctx, const Promise *pp);
FileRename GetRenameConstraints(const EvalContext *ctx, const Promise *pp);
FileSelect GetSelectConstraints(const EvalContext *ctx, const Promise *pp);
Expand Down
10 changes: 10 additions & 0 deletions libpromises/cf3.defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,14 @@ typedef struct

/*************************************************************************/

typedef struct
{
int immutable;
int haveimmutable;
} FileFSAttrs;

/*************************************************************************/

typedef struct
{
char *newname;
Expand Down Expand Up @@ -1520,6 +1528,7 @@ typedef struct
FilePerms perms;
FileCopy copy;
FileDelete delete;
FileFSAttrs fsattrs;
char *content;
FileRename rename;
FileChange change;
Expand Down Expand Up @@ -1571,6 +1580,7 @@ typedef struct
int haveselect;
int haverename;
int havedelete;
int havefsattrs;
int haveperms;
int havechange;
int havecopy;
Expand Down
10 changes: 10 additions & 0 deletions libpromises/mod_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ static const ConstraintSyntax delete_constraints[] =

static const BodySyntax delete_body = BodySyntaxNew("delete", delete_constraints, NULL, SYNTAX_STATUS_NORMAL);

static const ConstraintSyntax fsattrs_constraints[] =
{
CONSTRAINT_SYNTAX_GLOBAL,
ConstraintSyntaxNewBool("immutable", "true to set / false to clear the immutable flag", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewNull()
};

static const BodySyntax fsattrs_body = BodySyntaxNew("fsattrs", fsattrs_constraints, NULL, SYNTAX_STATUS_NORMAL);

static const ConstraintSyntax rename_constraints[] =
{
CONSTRAINT_SYNTAX_GLOBAL,
Expand Down Expand Up @@ -339,6 +348,7 @@ static const ConstraintSyntax CF_FILES_BODIES[] =
ConstraintSyntaxNewBody("copy_from", &copy_from_body, "Criteria for copying file from a source", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBool("create", "true/false whether to create non-existing file. Default value: false", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBody("delete", &delete_body, "Criteria for deleting files", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBody("fsattrs", &fsattrs_body, "Control file system attributes", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewString("content", CF_ANYSTRING, "Complete content the promised file should contain", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBody("depth_search", &depth_search_body, "Criteria for file depth searches", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBody("edit_defaults", &edit_defaults_body, "Default promise details for file edits", SYNTAX_STATUS_NORMAL),
Expand Down
75 changes: 75 additions & 0 deletions tests/acceptance/10_files/14_immutable/00_immutable.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
##############################################################################
#
# Test that immutable file CANNOT be edited by agent without specifying
# fsattrs immutable constraint.
#
##############################################################################

body common control
{
inputs => { "../../default.cf.sub" };
bundlesequence => { default("$(this.promise_filename)") };
version => "1.0";
}

body fsattrs set_immutable
{
immutable => "true";
}

body fsattrs set_mutable
{
immutable => "false";
}

bundle agent init
{
files:
"/tmp/00_immutable.txt"
content => "I'm immutable",
fsattrs => set_immutable;
}

bundle agent test
{
meta:
"description" -> { "CFE-1840", "ENT-10961" }
string => "Test that immutable file cannot be edited by agent without specifying fsattrs immutable constraint";

"test_soft_fail"
string => "hpux|aix|windows",
meta => { "CFE-1840", "ENT-10961" };

commands:
"$(sys.cf_agent) -Kf $(this.promise_filename).sub";
}

bundle agent check
{
vars:
"expected"
string => "I'm immutable";
"actual"
string => readfile("/tmp/00_immutable.txt");

classes:
"ok"
expression => strcmp("$(actual)", "$(expected)");

methods:
"any"
usebundle => dcs_passif("ok", "$(this.promise_filename)"),
inherit => "true";

reports:
"Expected: '$(expected)', actual: '$(actual)'";
}

bundle agent destroy
{
files:
# Make sure immutable bit is not set
"/tmp/00_immutable.txt"
fsattrs => set_mutable,
delete => tidy;
}
12 changes: 12 additions & 0 deletions tests/acceptance/10_files/14_immutable/00_immutable.cf.sub
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
body fsattrs set_immutable
{
# Nothing here
}

bundle agent main
{
files:
"/tmp/00_immutable.txt"
content => "I'm mutable",
fsattrs => set_immutable;
}
Loading
Loading