Skip to content

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

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

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d7e8ff3
Added utility functions to override immutable bit
larsewi Mar 28, 2025
4ebc7b2
Added body syntax for controlling file system attributes
larsewi Jan 28, 2025
5243590
Files promise can now modify immutable bit in file system attributes
larsewi Feb 6, 2025
1e74204
Added argument to NOOP OverrideImmutable functions
larsewi Apr 1, 2025
c07955f
Combined OverrideImmutable[Commit|Abort] functions
larsewi Apr 1, 2025
d5ada41
Content attribute can now override immutable bit
larsewi Apr 1, 2025
c9ba31d
Store override_immutable variable in EvalContext
larsewi Apr 3, 2025
0c53319
Added function to override immutable bit and rename file
larsewi Apr 3, 2025
83e0ed8
copy_from attribute can now override immutable bit
larsewi Apr 3, 2025
b69401b
Added function to override immutable bit and delete file
larsewi Apr 3, 2025
f8db2b0
delete attribute can now override immutable bit
larsewi Apr 3, 2025
1e6b326
edit_line and edit_xml attributes can now override immutable bit
larsewi Apr 4, 2025
d1f222a
override_fsattrs.c: refactored code
larsewi May 22, 2025
2cf862f
Touch attribute can now override immutable bit
larsewi May 22, 2025
048e9a6
Made functions for temporarily clear/reset immutable bit public
larsewi May 23, 2025
d5394a0
Transformer attribute can now override immutable bit
larsewi May 23, 2025
81bc75e
OverrideImmutableRename() new file inherits immutable flag of old
larsewi May 26, 2025
2db3cf9
Rename attribute can now override immutable bit
larsewi May 26, 2025
ec88ef0
Perms attribute can now override immutable bit
larsewi May 26, 2025
1823cff
acl attribute can now override immutable bit
larsewi May 26, 2025
dab2d99
evalfunction.c: Removed trailing whitespace
larsewi May 28, 2025
983faa4
Added policy function to get ACLs
larsewi May 28, 2025
4b6c26e
Added acceptance test for getacls()
larsewi Jun 2, 2025
4053710
Added acceptance test to set immutable bit
larsewi Jun 13, 2025
c99718d
Added acceptance test to clear immutable bit
larsewi Jun 13, 2025
5b13fea
Added acceptance test for immutable with content
larsewi Jun 13, 2025
841a313
Added acceptance test for immutable with copy_from
larsewi Jun 13, 2025
f75ea3b
Added acceptance test for immutable with delete
larsewi Jun 13, 2025
8e85661
Added acceptance test for immutable with edit_line
larsewi Jun 13, 2025
9f2b689
Added acceptance test for immutable with edit_xml
larsewi Jun 13, 2025
23b6519
Added acceptance test for immutable with perms
larsewi Jun 13, 2025
6b0b70d
Added acceptance test for immutable with touch
larsewi Jun 13, 2025
587ed51
Added acceptance test for immutable with edit_template
larsewi Jun 13, 2025
4369f2f
Added acceptance test for immutable with acl
larsewi Jun 13, 2025
40a4547
Added acceptance test for immutable with transformer
larsewi Jun 13, 2025
b0ce49b
Added acceptance test for immutable with rename
larsewi Jun 13, 2025
c61fe03
Added context to log messages in OverrideImmutableCommit
larsewi Jun 13, 2025
7dc51ff
Make it more clear what the override argument does
larsewi Jun 13, 2025
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
9 changes: 9 additions & 0 deletions cf-agent/acl_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include <rlist.h>
#include <eval_context.h>
#include <unix.h> /* GetGroupID(), GetUserID() */
#include <override_fsattrs.h>
#include "fsattrs.h"

#ifdef HAVE_ACL_H
# include <acl.h>
Expand Down Expand Up @@ -389,6 +391,11 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method,
acl_free(acl_text_str);
return false;
}

bool was_immutable = false;
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
FSAttrsResult res = TemporarilyClearImmutableBit(changes_path, override_immutable, &was_immutable);

if ((retv = acl_set_file(changes_path, acl_type, acl_new)) != 0)
{
RecordFailure(ctx, pp, a,
Expand All @@ -406,6 +413,8 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method,
}
acl_free(acl_text_str);

ResetTemporarilyClearedImmutableBit(changes_path, override_immutable, res, was_immutable);

RecordChange(ctx, pp, a, "%s ACL on '%s' successfully changed", acl_type_str, file_path);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
}
Expand Down
8 changes: 4 additions & 4 deletions cf-agent/files_edit.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c
RecordNoChange(ctx, pp, a, "No edit changes to file '%s' need saving",
ec->filename);
}
else if (SaveItemListAsFile(ec->file_start, ec->changes_filename, a, ec->new_line_mode))
else if (SaveItemListAsFile(ctx, ec->file_start, ec->changes_filename, a, ec->new_line_mode))
{
RecordChange(ctx, pp, a, "Edited file '%s'", ec->filename);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
Expand All @@ -151,7 +151,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c
ec->filename);
}
}
else if (SaveXmlDocAsFile(ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
else if (SaveXmlDocAsFile(ctx, ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
{
RecordChange(ctx, pp, a, "Edited xml file '%s'", ec->filename);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
Expand Down Expand Up @@ -254,8 +254,8 @@ static bool SaveXmlCallback(const char *dest_filename, void *param,

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

bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
{
return SaveAsFile(&SaveXmlCallback, doc, file, a, new_line_mode);
return SaveAsFile(ctx, &SaveXmlCallback, doc, file, a, new_line_mode);
}
#endif /* HAVE_LIBXML2 */
2 changes: 1 addition & 1 deletion cf-agent/files_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec,

#ifdef HAVE_LIBXML2
bool LoadFileAsXmlDoc(xmlDocPtr *doc, const char *file, EditDefaults ed, bool only_checks);
bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file,
bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file,
const Attributes *a, NewLineMode new_line_mode);
#endif

Expand Down
133 changes: 130 additions & 3 deletions cf-agent/verify_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
#include <evalfunction.h>
#include <changes_chroot.h> /* PrepareChangesChroot(), RecordFileChangedInChroot() */
#include <cf3.defs.h>
#include <fsattrs.h>
#include <override_fsattrs.h>

static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp);
static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp);
Expand Down Expand Up @@ -345,6 +347,67 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
changes_path = chrooted_path;
}

bool is_immutable = false; /* We assume not in case of failure */
FSAttrsResult res = FSAttrsGetImmutableFlag(changes_path, &is_immutable);
if (res != FS_ATTRS_SUCCESS)
{
Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE,
"Failed to get the state of the immutable bit from file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
}

if (a.havefsattrs && a.fsattrs.haveimmutable && !a.fsattrs.immutable)
{
/* Here we only handle the clearing of the immutable the immutable
* bit. Later we'll handle the setting of the immutable bit. */
if (is_immutable)
{
res = FSAttrsUpdateImmutableFlag(changes_path, false);
switch (res)
{
case FS_ATTRS_SUCCESS:
RecordChange(ctx, pp, &a,
"Cleared the immutable bit on file '%s'",
changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
break;
case FS_ATTRS_FAILURE:
RecordFailure(ctx, pp, &a,
"Failed to clear the immutable bit on 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 and filesystems. Instead we
* will log a verbose message to make it apparent for the
* users. */
Log(LOG_LEVEL_VERBOSE,
"Failed to clear the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* File does not exist. Nothing to do really, but let's log a
* debug message for good measures */
Log(LOG_LEVEL_DEBUG,
"Failed to clear the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
}
}
else
{
RecordNoChange(ctx, pp, &a,
"The immutable bit is not set on file '%s' as promised",
changes_path);
}
}

/* If we encounter any promises to mutate the file and the immutable
* attribute in body fsattrs is "true", we will override the immutable bit
* by temporarily clearing it when ever needed. */
EvalContextOverrideImmutableSet(ctx, a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable);

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

if (a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable)
{
/* Here we only handle the setting of the immutable bit. Previously we
* handled the clearing of the immutable bit. */
if (is_immutable)
{
RecordNoChange(ctx, pp, &a,
"The immutable bit is already set on file '%s' as promised",
changes_path);
}
else
{
res = FSAttrsUpdateImmutableFlag(changes_path, true);
switch (res)
{
case FS_ATTRS_SUCCESS:
Log(LOG_LEVEL_VERBOSE, "Set the immutable bit on 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 set the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
case FS_ATTRS_NOT_SUPPORTED:
/* We will not treat this as a promise failure because this
* will happen on many platforms and filesystems. Instead we
* will log a verbose message to make it apparent for the
* users. */
Log(LOG_LEVEL_VERBOSE,
"Failed to set the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* File does not exist. Nothing to do really, but let's log a
* debug message for good measures */
Log(LOG_LEVEL_DEBUG,
"Failed to set the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
}
}
}

// Once more in case a file has been created as a result of editing or copying

exists = (lstat(changes_path, &osb) != -1);
Expand All @@ -603,6 +711,9 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
}

exit:
/* Reset this to false before next file promise */
EvalContextOverrideImmutableSet(ctx, false);

free(chrooted_path);
if (AttrHasNoAction(&a))
{
Expand Down Expand Up @@ -686,37 +797,53 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path,

if (!HashesMatch(existing_content_digest, promised_content_digest, CF_DEFAULT_DIGEST))
{
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (!MakingChanges(ctx, pp, attr, &result,
"update file '%s' with content '%s'",
path, attr->content))
{
return result;
}

FILE *f = safe_fopen(changes_path, "w");
char override_path[PATH_MAX];
if (!OverrideImmutableBegin(changes_path, override_path, sizeof(override_path), override_immutable))
{
RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path);
return PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}

FILE *f = safe_fopen(override_path, "w");
if (f == NULL)
{
RecordFailure(ctx, pp, attr, "Cannot open file '%s' for writing", path);
OverrideImmutableCommit(changes_path, override_path, override_immutable, true);
return PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}

bool override_abort = false;
Writer *w = FileWriter(f);
if (WriterWriteLen(w, attr->content, bytes_to_write) == bytes_to_write )
{
RecordChange(ctx, pp, attr,
"Updated file '%s' with content '%s'",
path, attr->content);

result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
}
else
{
RecordFailure(ctx, pp, attr,
"Failed to update file '%s' with content '%s'",
path, attr->content);
override_abort = true;
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
WriterClose(w);

if (!OverrideImmutableCommit(changes_path, override_path, override_immutable, override_abort))
{
RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
}

return result;
Expand Down Expand Up @@ -861,7 +988,7 @@ static PromiseResult RenderTemplateMustache(EvalContext *ctx,
edcontext->filename, message);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
else if (SaveAsFile(SaveBufferCallback, output_buffer,
else if (SaveAsFile(ctx, SaveBufferCallback, output_buffer,
edcontext->changes_filename, attr,
edcontext->new_line_mode))
{
Expand Down
Loading
Loading