From ghudson at mit.edu Wed Apr 17 18:26:07 2024 From: ghudson at mit.edu (ghudson at mit.edu) Date: Wed, 17 Apr 2024 18:26:07 -0400 (EDT) Subject: krb5 commit: Allow modifications of empty profiles Message-ID: <20240417222607.0BE891019F1@krbdev.mit.edu> https://github.com/krb5/krb5/commit/fc54edd1dc047aedb211beaa544c5e000fbdb7a6 commit fc54edd1dc047aedb211beaa544c5e000fbdb7a6 Author: Greg Hudson Date: Sun Mar 31 12:30:18 2024 -0400 Allow modifications of empty profiles Add the notion of a memory-only prf_data_t object, indicated by an empty filespec field and appropriate flags (do not reload, always dirty, not part of shared trees). Do nothing when flushing a memory-only data object to its backing file. When setting up an empty profile for read/write access, create a memory-only data object instead of crashing. Move prf_data_t mutex initialization into profile_make_prf_data(), simplifying its callers. ticket: 9110 src/util/profile/prof_file.c | 46 +++++++++++++++++++++++++++++++++++++------- src/util/profile/prof_int.h | 2 ++ src/util/profile/prof_set.c | 33 ++++++++++++++----------------- src/util/profile/t_profile.c | 28 +++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 26 deletions(-) diff --git a/src/util/profile/prof_file.c b/src/util/profile/prof_file.c index aa951df05..b5eddc0d9 100644 --- a/src/util/profile/prof_file.c +++ b/src/util/profile/prof_file.c @@ -159,6 +159,10 @@ profile_make_prf_data(const char *filename) d->root = NULL; d->next = NULL; d->fslen = flen; + if (k5_mutex_init(&d->lock) != 0) { + free(d); + return NULL; + } return d; } @@ -239,13 +243,6 @@ errcode_t profile_open_file(const_profile_filespec_t filespec, free(expanded_filename); prf->data = data; - retval = k5_mutex_init(&data->lock); - if (retval) { - free(data); - free(prf); - return retval; - } - retval = profile_update_file(prf, ret_modspec); if (retval) { profile_close_file(prf); @@ -262,6 +259,37 @@ errcode_t profile_open_file(const_profile_filespec_t filespec, return 0; } +prf_file_t profile_open_memory(void) +{ + struct profile_node *root = NULL; + prf_file_t file = NULL; + prf_data_t data; + + file = calloc(1, sizeof(*file)); + if (file == NULL) + goto errout; + file->magic = PROF_MAGIC_FILE; + + if (profile_create_node("(root)", NULL, &root) != 0) + goto errout; + + data = profile_make_prf_data(""); + if (data == NULL) + goto errout; + + data->root = root; + data->flags = PROFILE_FILE_NO_RELOAD | PROFILE_FILE_DIRTY; + file->data = data; + file->next = NULL; + return file; + +errout: + free(file); + if (root != NULL) + profile_free_node(root); + return NULL; +} + errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec) { errcode_t retval; @@ -468,6 +496,10 @@ errcode_t profile_flush_file_data(prf_data_t data) if (!data || data->magic != PROF_MAGIC_FILE_DATA) return PROF_MAGIC_FILE_DATA; + /* Do nothing if this data object has no backing file. */ + if (*data->filespec == '\0') + return 0; + k5_mutex_lock(&data->lock); if ((data->flags & PROFILE_FILE_DIRTY) == 0) { diff --git a/src/util/profile/prof_int.h b/src/util/profile/prof_int.h index 1ee9a8ca1..21c535a5c 100644 --- a/src/util/profile/prof_int.h +++ b/src/util/profile/prof_int.h @@ -214,6 +214,8 @@ errcode_t profile_open_file (const_profile_filespec_t file, prf_file_t *ret_prof, char **ret_modspec); +prf_file_t profile_open_memory(void); + #define profile_update_file(P, M) profile_update_file_data((P)->data, M) errcode_t profile_update_file_data (prf_data_t profile, char **ret_modspec); diff --git a/src/util/profile/prof_set.c b/src/util/profile/prof_set.c index af4b2f853..aeea676cb 100644 --- a/src/util/profile/prof_set.c +++ b/src/util/profile/prof_set.c @@ -24,7 +24,7 @@ static errcode_t rw_setup(profile_t profile) { prf_file_t file; - errcode_t retval = 0; + prf_data_t new_data; if (!profile) return PROF_NO_PROFILE; @@ -32,6 +32,12 @@ static errcode_t rw_setup(profile_t profile) if (profile->magic != PROF_MAGIC_PROFILE) return PROF_MAGIC_PROFILE; + /* If the profile has no files, create a memory-only data object. */ + if (profile->first_file == NULL) { + profile->first_file = profile_open_memory(); + return (profile->first_file == NULL) ? ENOMEM : 0; + } + file = profile->first_file; profile_lock_global(); @@ -43,33 +49,22 @@ static errcode_t rw_setup(profile_t profile) } if ((file->data->flags & PROFILE_FILE_SHARED) != 0) { - prf_data_t new_data; new_data = profile_make_prf_data(file->data->filespec); if (new_data == NULL) { - retval = ENOMEM; - } else { - retval = k5_mutex_init(&new_data->lock); - if (retval == 0) { - new_data->root = NULL; - new_data->flags = file->data->flags & ~PROFILE_FILE_SHARED; - new_data->timestamp = 0; - new_data->upd_serial = file->data->upd_serial; - } - } - - if (retval != 0) { profile_unlock_global(); - free(new_data); - return retval; + return ENOMEM; } + new_data->root = NULL; + new_data->flags = file->data->flags & ~PROFILE_FILE_SHARED; + new_data->timestamp = 0; + new_data->upd_serial = file->data->upd_serial; + profile_dereference_data_locked(file->data); file->data = new_data; } profile_unlock_global(); - retval = profile_update_file(file, NULL); - - return retval; + return profile_update_file(file, NULL); } diff --git a/src/util/profile/t_profile.c b/src/util/profile/t_profile.c index bffd11561..0e859b97c 100644 --- a/src/util/profile/t_profile.c +++ b/src/util/profile/t_profile.c @@ -373,6 +373,33 @@ test_merge_subsections(void) profile_release(p); } +/* Regression test for #9110 (null dereference when modifying an empty + * profile), and various other operations on an initially empty profile. */ +static void +test_empty(void) +{ + profile_t p; + const char *n1[] = { "section", NULL }; + const char *n2[] = { "section", "var", NULL }; + char **values; + + check(profile_init(NULL, &p)); + check(profile_add_relation(p, n1, NULL)); + check(profile_add_relation(p, n2, "value")); + check(profile_flush(p)); /* should succeed but do nothing */ + check(profile_get_values(p, n2, &values)); + assert(strcmp(values[0], "value") == 0 && values[1] == NULL); + profile_free_list(values); + profile_flush_to_file(p, "test3.ini"); + profile_release(p); + + profile_init_path("test3.ini", &p); + check(profile_get_values(p, n2, &values)); + assert(strcmp(values[0], "value") == 0 && values[1] == NULL); + profile_free_list(values); + profile_release(p); +} + int main(void) { @@ -386,4 +413,5 @@ main(void) test_delete_ordering(); test_flush_to_file(); test_merge_subsections(); + test_empty(); } From ghudson at mit.edu Sat Apr 20 20:19:30 2024 From: ghudson at mit.edu (ghudson at mit.edu) Date: Sat, 20 Apr 2024 20:19:30 -0400 (EDT) Subject: krb5 commit: Improve profile final flag support Message-ID: <20240421001930.A8860101905@krbdev.mit.edu> https://github.com/krb5/krb5/commit/f951625e6bd3ff44f1056958b56e35a1a043e362 commit f951625e6bd3ff44f1056958b56e35a1a043e362 Author: Greg Hudson Date: Mon Apr 15 18:47:27 2024 -0400 Improve profile final flag support When parsing a file, ignore sections appearing after a final-flagged section of the same name. Adjust the meaning of group_level in the parser state so that it is 1 inside of top-level sections instead of 0, and simplify the addition of top-level sections to the tree by relying on profile_add_node()'s section merging. Make the final flag work for relations as well as sections. Check it while parsing via a new check_final parameter in profile_add_node(), and during iteration. Output final flags for relations in dump_profile(). Make the final flag available to it via a new output parameter in profile_find_node_relation(). ticket: 9120 doc/admin/conf_files/krb5_conf.rst | 16 +++++--- src/util/profile/Makefile.in | 8 ++++ src/util/profile/final.expected | 18 ++++++++- src/util/profile/final2.ini | 4 +- src/util/profile/final6.ini | 45 ++++++++++++++++++++++ src/util/profile/prof_int.h | 4 +- src/util/profile/prof_parse.c | 78 ++++++++++++++++++++------------------ src/util/profile/prof_set.c | 4 +- src/util/profile/prof_tree.c | 19 ++++++++-- 9 files changed, 143 insertions(+), 53 deletions(-) diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst index 1b92170da..ab73a694d 100644 --- a/doc/admin/conf_files/krb5_conf.rst +++ b/doc/admin/conf_files/krb5_conf.rst @@ -35,12 +35,6 @@ or:: baz = quux } -Placing a '\*' after the closing bracket of a section name indicates -that the section is *final*, meaning that if the same section appears -within a later file specified in **KRB5_CONFIG**, it will be ignored. -A subsection can be marked as final by placing a '\*' after either the -tag name or the closing brace. - The krb5.conf file can include other files using either of the following directives at the beginning of a line:: @@ -58,6 +52,16 @@ section header. Starting in release 1.17, files are read in alphanumeric order; in previous releases, they may be read in any order. +Placing a '\*' after the closing bracket of a section name indicates +that the section is *final*, meaning that if the same section appears +again later, it will be ignored. A subsection can be marked as final +by placing a '\*' after either the tag name or the closing brace. A +relation can be marked as final by placing a '\*' after the tag name. +Prior to release 1.22, only sections and subsections can be marked as +final, and the flag only causes values to be ignored if they appear in +later files specified in **KRB5_CONFIG**, not if they appear later +within the same file or an included file. + The krb5.conf file can specify that configuration should be obtained from a loadable module, rather than the file itself, using the following directive at the beginning of a line before any section diff --git a/src/util/profile/Makefile.in b/src/util/profile/Makefile.in index 18c6d2dd5..e4316e1f7 100644 --- a/src/util/profile/Makefile.in +++ b/src/util/profile/Makefile.in @@ -140,6 +140,7 @@ F2=$(srcdir)/final2.ini F3=$(srcdir)/final3.ini F4=$(srcdir)/final4.ini F5=$(srcdir)/final5.ini +F6=$(srcdir)/final6.ini QUERY=query section subsection key check-unix-final: test_profile $(RM) final.out @@ -148,6 +149,13 @@ check-unix-final: test_profile (echo; $(RUN_TEST) ./test_profile $(F3):$(F1) $(QUERY)) >> final.out (echo; $(RUN_TEST) ./test_profile $(F4):$(F1) $(QUERY)) >> final.out (echo; $(RUN_TEST) ./test_profile $(F5):$(F1) $(QUERY)) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query a ab) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query a ac) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query b ba) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query b bb bba) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query c ca caa) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query c cb cba) >> final.out + (echo; $(RUN_TEST) ./test_profile $(F6) query c cc) >> final.out cmp final.out $(srcdir)/final.expected $(RM) final.out diff --git a/src/util/profile/final.expected b/src/util/profile/final.expected index fb23b3ccc..1d5354378 100644 --- a/src/util/profile/final.expected +++ b/src/util/profile/final.expected @@ -3,10 +3,26 @@ value1 value1 value2 -value1 value3 value4 value5 + +1 +2 + +1 + +1 +2 + +1 + +1 + +1 + +1 +2 diff --git a/src/util/profile/final2.ini b/src/util/profile/final2.ini index 4b1e15b79..827ec25dc 100644 --- a/src/util/profile/final2.ini +++ b/src/util/profile/final2.ini @@ -1,6 +1,4 @@ -# In this variant the relation is marked final. There is parsing -# support for this but no iteration or dumping support, so the marker -# currently has no effect. +# In this variant the relation is marked final. [section] subsection = { key* = value2 diff --git a/src/util/profile/final6.ini b/src/util/profile/final6.ini new file mode 100644 index 000000000..c1e44b747 --- /dev/null +++ b/src/util/profile/final6.ini @@ -0,0 +1,45 @@ +# A profile exercising suppression of final-flagged sections, +# subsections, and relations within the same file. + +[a] + ab = 1 + ab* = 2 + ab = 3 + ab* = 4 + ac* = 1 + +[a] + ac = 2 + ac* = 3 + ac = 4 + +[b]* + ba = 1 + ba* = 2 + bb = { + bba = 1 + } + +[b] + ba = 3 + bb = { + bba = 2 + } + +[c] + ca* = { + caa* = 1 + } + cb = { + cba = 1 + }* + cc = 1 + +[c] + ca = { + caa = 2 + } + cb* = { + cba = 2 + } + cc = 2 diff --git a/src/util/profile/prof_int.h b/src/util/profile/prof_int.h index 21c535a5c..95f7b346f 100644 --- a/src/util/profile/prof_int.h +++ b/src/util/profile/prof_int.h @@ -144,7 +144,7 @@ errcode_t profile_verify_node errcode_t profile_add_node (struct profile_node *section, - const char *name, const char *value, + const char *name, const char *value, int check_final, struct profile_node **ret_node); errcode_t profile_make_node_final @@ -168,7 +168,7 @@ errcode_t profile_find_node errcode_t profile_find_node_relation (struct profile_node *section, const char *name, void **state, - char **ret_name, char **value); + char **ret_name, char **value, int *ret_final); errcode_t profile_find_node_subsection (struct profile_node *section, diff --git a/src/util/profile/prof_parse.c b/src/util/profile/prof_parse.c index 7ba44aca6..c581fb722 100644 --- a/src/util/profile/prof_parse.c +++ b/src/util/profile/prof_parse.c @@ -20,8 +20,9 @@ #define STATE_GET_OBRACE 3 struct parse_state { - int state; - int group_level; + int state; + int group_level; + int discard; /* group_level of a final-flagged section */ struct profile_node *root_section; struct profile_node *current_section; }; @@ -78,7 +79,6 @@ static errcode_t parse_std_line(char *line, struct parse_state *state) errcode_t retval; struct profile_node *node; int do_subsection = 0; - void *iter = 0; if (*line == 0) return 0; @@ -90,24 +90,22 @@ static errcode_t parse_std_line(char *line, struct parse_state *state) if (ch == 0) return 0; if (ch == '[') { - if (state->group_level > 0) + if (state->group_level > 1) return PROF_SECTION_NOTOP; cp++; p = strchr(cp, ']'); if (p == NULL) return PROF_SECTION_SYNTAX; *p = '\0'; - retval = profile_find_node_subsection(state->root_section, - cp, &iter, 0, - &state->current_section); - if (retval == PROF_NO_SECTION) { - retval = profile_add_node(state->root_section, - cp, 0, - &state->current_section); - if (retval) - return retval; - } else if (retval) + retval = profile_add_node(state->root_section, cp, NULL, 0, + &state->current_section); + if (retval) return retval; + state->group_level = 1; + /* If we previously saw this section name with the final flag, + * discard values until the next top-level section. */ + state->discard = profile_is_node_final(state->current_section) ? + 1 : 0; /* * Finish off the rest of the line. @@ -135,6 +133,9 @@ static errcode_t parse_std_line(char *line, struct parse_state *state) if (retval) return retval; state->group_level--; + /* Check if we are done discarding values from a subsection. */ + if (state->group_level < state->discard) + state->discard = 0; return 0; } /* @@ -180,21 +181,29 @@ static errcode_t parse_std_line(char *line, struct parse_state *state) p = strchr(tag, '*'); if (p) *p = '\0'; - retval = profile_add_node(state->current_section, - tag, 0, &state->current_section); - if (retval) - return retval; - if (p) - profile_make_node_final(state->current_section); state->group_level++; + if (!state->discard) { + retval = profile_add_node(state->current_section, tag, NULL, 0, + &state->current_section); + if (retval) + return retval; + /* If we previously saw this subsection with the final flag, + * discard values until the subsection is done. */ + if (profile_is_node_final(state->current_section)) + state->discard = state->group_level; + if (p) + profile_make_node_final(state->current_section); + } return 0; } p = strchr(tag, '*'); if (p) *p = '\0'; - profile_add_node(state->current_section, tag, value, &node); - if (p) - profile_make_node_final(node); + if (!state->discard) { + profile_add_node(state->current_section, tag, value, 1, &node); + if (p && node) + profile_make_node_final(node); + } return 0; } @@ -209,7 +218,7 @@ static errcode_t parse_include_file(const char *filename, /* Create a new state so that fragments are syntactically independent but * share a root section. */ state.state = STATE_INIT_COMMENT; - state.group_level = 0; + state.group_level = state.discard = 0; state.root_section = root_section; state.current_section = NULL; @@ -407,7 +416,7 @@ errcode_t profile_parse_file(FILE *f, struct profile_node **root, /* Initialize parsing state with a new root node. */ state.state = STATE_INIT_COMMENT; - state.group_level = 0; + state.group_level = state.discard = 0; state.current_section = NULL; retval = profile_create_node("(root)", 0, &state.root_section); if (retval) @@ -513,7 +522,7 @@ static void output_quoted_string(char *str, void (*cb)(const char *,void *), static void dump_profile(struct profile_node *root, int level, void (*cb)(const char *, void *), void *data) { - int i; + int i, final; struct profile_node *p; void *iter; long retval; @@ -522,22 +531,19 @@ static void dump_profile(struct profile_node *root, int level, iter = 0; do { retval = profile_find_node_relation(root, 0, &iter, - &name, &value); + &name, &value, &final); if (retval) break; for (i=0; i < level; i++) cb("\t", data); - if (need_double_quotes(value)) { - cb(name, data); - cb(" = ", data); + cb(name, data); + cb(final ? "*" : "", data); + cb(" = ", data); + if (need_double_quotes(value)) output_quoted_string(value, cb, data); - cb(EOL, data); - } else { - cb(name, data); - cb(" = ", data); + else cb(value, data); - cb(EOL, data); - } + cb(EOL, data); } while (iter != 0); iter = 0; diff --git a/src/util/profile/prof_set.c b/src/util/profile/prof_set.c index aeea676cb..e4971d12f 100644 --- a/src/util/profile/prof_set.c +++ b/src/util/profile/prof_set.c @@ -270,7 +270,7 @@ profile_add_relation(profile_t profile, const char **names, retval = profile_find_node(section, *cpp, 0, 1, &state, §ion); if (retval == PROF_NO_SECTION) - retval = profile_add_node(section, *cpp, 0, §ion); + retval = profile_add_node(section, *cpp, NULL, 0, §ion); if (retval) { k5_mutex_unlock(&profile->first_file->data->lock); return retval; @@ -289,7 +289,7 @@ profile_add_relation(profile_t profile, const char **names, } } - retval = profile_add_node(section, *cpp, new_value, 0); + retval = profile_add_node(section, *cpp, new_value, 0, NULL); if (retval) { k5_mutex_unlock(&profile->first_file->data->lock); return retval; diff --git a/src/util/profile/prof_tree.c b/src/util/profile/prof_tree.c index b3c15ca1c..b6bdbf3f6 100644 --- a/src/util/profile/prof_tree.c +++ b/src/util/profile/prof_tree.c @@ -145,10 +145,12 @@ errcode_t profile_verify_node(struct profile_node *node) } /* - * Add a node to a particular section + * Add a node to a particular section. If check_final is true, don't add the + * node if we find a final node for the same name. */ errcode_t profile_add_node(struct profile_node *section, const char *name, - const char *value, struct profile_node **ret_node) + const char *value, int check_final, + struct profile_node **ret_node) { errcode_t retval; struct profile_node *p, *last, *new; @@ -174,6 +176,12 @@ errcode_t profile_add_node(struct profile_node *section, const char *name, /* Found duplicate subsection, so don't make a new one. */ *ret_node = p; return 0; + } else if (check_final && cmp == 0 && p->final) { + /* This key already exists with the final flag and we were asked + * to check it, so don't add this node. */ + if (ret_node) + *ret_node = NULL; + return 0; } } retval = profile_create_node(name, value, &new); @@ -326,7 +334,8 @@ errcode_t profile_find_node(struct profile_node *section, const char *name, */ errcode_t profile_find_node_relation(struct profile_node *section, const char *name, void **state, - char **ret_name, char **value) + char **ret_name, char **value, + int *ret_final) { struct profile_node *p; errcode_t retval; @@ -340,6 +349,8 @@ errcode_t profile_find_node_relation(struct profile_node *section, *value = p->value; if (ret_name) *ret_name = p->name; + if (ret_final) + *ret_final = p->final; } return 0; } @@ -569,6 +580,8 @@ get_new_file: } if (p->deleted) continue; + if (p->final) + iter->flags |= PROFILE_ITER_FINAL_SEEN; break; } iter->num++; From ghudson at mit.edu Sat Apr 20 20:50:55 2024 From: ghudson at mit.edu (ghudson at mit.edu) Date: Sat, 20 Apr 2024 20:50:55 -0400 (EDT) Subject: krb5 commit: Do not reload a modified profile data object Message-ID: <20240421005055.862B8101905@krbdev.mit.edu> https://github.com/krb5/krb5/commit/9b2fb80ad24006784170875709a04dc79e03b401 commit 9b2fb80ad24006784170875709a04dc79e03b401 Author: Greg Hudson Date: Tue Apr 16 02:14:29 2024 -0400 Do not reload a modified profile data object The profile library normally attempts to reload a profile data tree if the backing file has changed. Reloading a dirty profile object discards any modifications made by the caller. If we assume that the modifications are destined to be flushed back out to the backing file, then there is no good answer--one or the other set of changes will be lost. But the caller may have a different intended use for the modified tree (profile_flush_to_file(), profile_flush_to_buffer(), krb5_init_context_profile()), for which the caller's modifications may be critical. Avoid discarding in-memory edits to ensure the correctness of these use cases. ticket: 9118 src/util/profile/prof_file.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/util/profile/prof_file.c b/src/util/profile/prof_file.c index b5eddc0d9..a39a5dc18 100644 --- a/src/util/profile/prof_file.c +++ b/src/util/profile/prof_file.c @@ -301,8 +301,13 @@ errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec) FILE *f; int isdir = 0; + /* Don't reload if the backing file isn't a regular file. */ if ((data->flags & PROFILE_FILE_NO_RELOAD) && data->root != NULL) return 0; + /* Don't reload a modified data object, as the modifications may be + * important for this object's use. */ + if (data->flags & PROFILE_FILE_DIRTY) + return 0; #ifdef HAVE_STAT now = time(0); @@ -358,7 +363,6 @@ errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec) } data->upd_serial++; - data->flags &= ~PROFILE_FILE_DIRTY; if (isdir) { retval = profile_process_directory(data->filespec, &data->root); From ghudson at mit.edu Mon Apr 22 20:39:31 2024 From: ghudson at mit.edu (ghudson at mit.edu) Date: Mon, 22 Apr 2024 20:39:31 -0400 (EDT) Subject: krb5 commit: Make profile_copy() work on dirty profiles Message-ID: <20240423003931.D04331019B8@krbdev.mit.edu> https://github.com/krb5/krb5/commit/078721b1f4a8fb995e0d5346ecf36adffd0a4f99 commit 078721b1f4a8fb995e0d5346ecf36adffd0a4f99 Author: Greg Hudson Date: Wed Apr 3 14:13:50 2024 -0400 Make profile_copy() work on dirty profiles Replace the current implementation of profile_copy() with one that copies the in-memory tree structure of non-shared data objects. Make profile_copy() a public function. ticket: 9119 (new) src/util/profile/prof_file.c | 38 +++++++++++++++++++++++++++++++++++++ src/util/profile/prof_init.c | 36 ++++++++++++++++++++--------------- src/util/profile/prof_int.h | 8 ++++++-- src/util/profile/prof_tree.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ src/util/profile/profile.hin | 3 +++ src/util/profile/t_profile.c | 9 ++++++++- 6 files changed, 121 insertions(+), 18 deletions(-) diff --git a/src/util/profile/prof_file.c b/src/util/profile/prof_file.c index a39a5dc18..5567903ce 100644 --- a/src/util/profile/prof_file.c +++ b/src/util/profile/prof_file.c @@ -554,6 +554,44 @@ void profile_unlock_global() k5_mutex_unlock(&g_shared_trees_mutex); } +prf_file_t profile_copy_file(prf_file_t oldfile) +{ + prf_file_t file; + + file = calloc(1, sizeof(*file)); + if (file == NULL) + return NULL; + file->magic = PROF_MAGIC_FILE; + + /* Shared data objects can just have their reference counts incremented. */ + if (oldfile->data->flags & PROFILE_FILE_SHARED) { + profile_lock_global(); + oldfile->data->refcount++; + profile_unlock_global(); + file->data = oldfile->data; + return file; + } + + /* Otherwise we need to copy the data object. */ + file->data = profile_make_prf_data(oldfile->data->filespec); + if (file->data == NULL) { + free(file); + return NULL; + } + k5_mutex_lock(&oldfile->data->lock); + file->data->flags = oldfile->data->flags; + file->data->last_stat = oldfile->data->last_stat; + file->data->frac_ts = oldfile->data->frac_ts; + file->data->root = profile_copy_node(oldfile->data->root); + k5_mutex_unlock(&oldfile->data->lock); + if (file->data->root == NULL) { + profile_free_file(file); + return NULL; + } + + return file; +} + void profile_free_file(prf_file_t prf) { profile_dereference_data(prf->data); diff --git a/src/util/profile/prof_init.c b/src/util/profile/prof_init.c index c6c48b594..1cf7a9451 100644 --- a/src/util/profile/prof_init.c +++ b/src/util/profile/prof_init.c @@ -293,26 +293,32 @@ copy_vtable_profile(profile_t profile, profile_t *ret_new_profile) errcode_t KRB5_CALLCONV profile_copy(profile_t old_profile, profile_t *new_profile) { - size_t size, i; - const_profile_filespec_t *files; - prf_file_t file; - errcode_t err; + profile_t profile; + prf_file_t p, q, *nextp; + + *new_profile = NULL; if (old_profile->vt) return copy_vtable_profile(old_profile, new_profile); - /* The fields we care about are read-only after creation, so - no locking is needed. */ - COUNT_LINKED_LIST (size, prf_file_t, old_profile->first_file, next); - files = malloc ((size+1) * sizeof(*files)); - if (files == NULL) + profile = calloc(1, sizeof(*profile)); + if (profile == NULL) return ENOMEM; - for (i = 0, file = old_profile->first_file; i < size; i++, file = file->next) - files[i] = file->data->filespec; - files[size] = NULL; - err = profile_init (files, new_profile); - free (files); - return err; + profile->magic = PROF_MAGIC_PROFILE; + + nextp = &profile->first_file; + for (p = old_profile->first_file; p != NULL; p = p->next) { + q = profile_copy_file(p); + if (q == NULL) { + profile_abandon(profile); + return ENOMEM; + } + *nextp = q; + nextp = &q->next; + } + + *new_profile = profile; + return 0; } errcode_t KRB5_CALLCONV diff --git a/src/util/profile/prof_int.h b/src/util/profile/prof_int.h index 95f7b346f..6bd30a373 100644 --- a/src/util/profile/prof_int.h +++ b/src/util/profile/prof_int.h @@ -139,6 +139,9 @@ errcode_t profile_create_node (const char *name, const char *value, struct profile_node **ret_node); +struct profile_node *profile_copy_node + (struct profile_node *oldnode); + errcode_t profile_verify_node (struct profile_node *node); @@ -208,8 +211,6 @@ errcode_t profile_rename_node /* prof_file.c */ -errcode_t KRB5_CALLCONV profile_copy (profile_t, profile_t *); - errcode_t profile_open_file (const_profile_filespec_t file, prf_file_t *ret_prof, char **ret_modspec); @@ -234,6 +235,9 @@ errcode_t profile_flush_file_data_to_file errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp); +prf_file_t profile_copy_file + (prf_file_t oldfile); + void profile_free_file (prf_file_t profile); diff --git a/src/util/profile/prof_tree.c b/src/util/profile/prof_tree.c index b6bdbf3f6..3e2aaa1cf 100644 --- a/src/util/profile/prof_tree.c +++ b/src/util/profile/prof_tree.c @@ -112,6 +112,51 @@ errcode_t profile_create_node(const char *name, const char *value, return 0; } +/* Return a copy of oldnode. Recursively copy oldnode's children, but leave + * the parent, next, and prev pointers as null. */ +struct profile_node *profile_copy_node(struct profile_node *oldnode) +{ + struct profile_node *node = NULL, *p, *q, **nextp, *last; + + if (oldnode->magic != PROF_MAGIC_NODE) + return NULL; + + node = calloc(1, sizeof(*node)); + node->magic = PROF_MAGIC_NODE; + node->name = strdup(oldnode->name); + if (node->name == NULL) + goto errout; + if (oldnode->value != NULL) { + node->value = strdup(oldnode->value); + if (oldnode->value == NULL) + goto errout; + } + node->group_level = oldnode->group_level; + node->final = oldnode->final; + node->deleted = oldnode->deleted; + + nextp = &node->first_child; + last = NULL; + for (p = oldnode->first_child; p != NULL; p = p->next) { + q = profile_copy_node(p); + if (q == NULL) + goto errout; + + /* Link in the new child and prepare for the next. */ + q->parent = node; + q->prev = last; + last = q; + *nextp = q; + nextp = &q->next; + } + + return node; + +errout: + profile_free_node(node); + return NULL; +} + /* * This function verifies that all of the representation invariants of * the profile are true. If not, we have a programming bug somewhere, diff --git a/src/util/profile/profile.hin b/src/util/profile/profile.hin index 45ad55430..fef2b62f2 100644 --- a/src/util/profile/profile.hin +++ b/src/util/profile/profile.hin @@ -51,6 +51,9 @@ long KRB5_CALLCONV profile_init_flags long KRB5_CALLCONV profile_init_path (const_profile_filespec_list_t filelist, profile_t *ret_profile); +long KRB5_CALLCONV profile_copy + (profile_t, profile_t *); + long KRB5_CALLCONV profile_flush (profile_t profile); long KRB5_CALLCONV profile_flush_to_file diff --git a/src/util/profile/t_profile.c b/src/util/profile/t_profile.c index 0e859b97c..fd3b65ed8 100644 --- a/src/util/profile/t_profile.c +++ b/src/util/profile/t_profile.c @@ -378,7 +378,7 @@ test_merge_subsections(void) static void test_empty(void) { - profile_t p; + profile_t p, p2; const char *n1[] = { "section", NULL }; const char *n2[] = { "section", "var", NULL }; char **values; @@ -390,6 +390,13 @@ test_empty(void) check(profile_get_values(p, n2, &values)); assert(strcmp(values[0], "value") == 0 && values[1] == NULL); profile_free_list(values); + + check(profile_copy(p, &p2)); + check(profile_get_values(p2, n2, &values)); + assert(strcmp(values[0], "value") == 0 && values[1] == NULL); + profile_free_list(values); + profile_release(p2); + profile_flush_to_file(p, "test3.ini"); profile_release(p); From ghudson at mit.edu Tue Apr 23 17:43:54 2024 From: ghudson at mit.edu (ghudson at mit.edu) Date: Tue, 23 Apr 2024 17:43:54 -0400 (EDT) Subject: krb5 commit: Don't flush libkrb5 context profiles Message-ID: <20240423214354.2F02B1019CD@krbdev.mit.edu> https://github.com/krb5/krb5/commit/0a3acc20564e82ba33741248cf25ca4d085d777f commit 0a3acc20564e82ba33741248cf25ca4d085d777f Author: Greg Hudson Date: Wed Apr 3 14:20:53 2024 -0400 Don't flush libkrb5 context profiles The profile library has two deconstructors, profile_release() and profile_abandon(). profile_release() flushes in-memory changes to backing files, while profile_abandon() does not. If a krb5_context profile contains in-memory changes, they were copied from a profile supplied to krb5_init_context_profile(), and the caller can decide whether to flush them. As profile_copy() is now a public function, remove the include of prof_int.h and the associated LOCALINCLUDES setting in Makefile.in. ticket: 9121 (new) .gitignore | 1 + src/lib/krb5/os/Makefile.in | 16 ++++++--- src/lib/krb5/os/deps | 14 ++++++-- src/lib/krb5/os/init_os_ctx.c | 5 ++- src/lib/krb5/os/t_ctxprf.c | 79 +++++++++++++++++++++++++++++++++++++++++++ src/lib/krb5/os/t_ctxprf.py | 15 ++++++++ 6 files changed, 120 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 00e42adf0..bfbf45ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -358,6 +358,7 @@ local.properties /src/lib/krb5/krb/t_sname_match /src/lib/krb5/krb/t_valid_times +/src/lib/krb5/os/t_ctxprf /src/lib/krb5/os/t_expand_path /src/lib/krb5/os/t_locate_kdc /src/lib/krb5/os/t_std_conf diff --git a/src/lib/krb5/os/Makefile.in b/src/lib/krb5/os/Makefile.in index f523a5ac8..e84b5b747 100644 --- a/src/lib/krb5/os/Makefile.in +++ b/src/lib/krb5/os/Makefile.in @@ -2,7 +2,6 @@ mydir=lib$(S)krb5$(S)os BUILDTOP=$(REL)..$(S)..$(S).. DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\" -DBINDIR=\"$(CLIENT_BINDIR)\" \ -DSBINDIR=\"$(ADMIN_BINDIR)\" -LOCALINCLUDES= -I$(top_srcdir)/util/profile # Like RUN_TEST, but use td_krb5.conf from this directory. RUN_TEST_LOCAL_CONF=$(RUN_SETUP) KRB5_CONFIG=$(srcdir)/td_krb5.conf LC_ALL=C \ @@ -154,7 +153,8 @@ SRCS= \ $(srcdir)/write_msg.c EXTRADEPSRCS = \ - t_expand_path.c t_gifconf.c t_locate_kdc.c t_std_conf.c t_trace.c + t_ctxprf.c t_expand_path.c t_gifconf.c t_locate_kdc.c t_std_conf.c \ + t_trace.c ##DOS##LIBOBJS = $(OBJS) @@ -164,7 +164,7 @@ clean-unix:: clean-libobjs shared: mkdir shared -TEST_PROGS= t_std_conf t_locate_kdc t_trace t_expand_path +TEST_PROGS= t_std_conf t_locate_kdc t_trace t_expand_path t_ctxprf T_STD_CONF_OBJS= t_std_conf.o @@ -190,6 +190,9 @@ t_trace: $(T_TRACE_OBJS) $(KRB5_BASE_DEPLIBS) t_expand_path: t_expand_path.o $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o $@ t_expand_path.o $(KRB5_BASE_LIBS) +t_ctxprf: t_ctxprf.o $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ t_ctxprf.o $(KRB5_BASE_LIBS) + LCLINT=lclint LCLINTOPTS= -warnposix \ -usedef +charintliteral +ignoresigns -predboolint +boolint \ @@ -200,7 +203,7 @@ lclint-localaddr: localaddr.c -DTEST $(srcdir)/localaddr.c check-unix: check-unix-stdconf check-unix-locate check-unix-trace \ - check-unix-expand check-unix-uri + check-unix-expand check-unix-uri check-unix-ctxprf check-unix-stdconf: t_std_conf $(RUN_TEST_LOCAL_CONF) ./t_std_conf -d -s NEW.DEFAULT.REALM -d \ @@ -259,9 +262,12 @@ check-unix-expand: t_expand_path 'the %{animal}%{s} on the %{place}%{s}' \ 'the frogs on the pads' +check-unix-ctxprf: t_ctxprf + $(RUNPYTEST) $(srcdir)/t_ctxprf.py $(PYTESTFLAGS) + clean: $(RM) $(TEST_PROGS) test.out t_std_conf.o t_locate_kdc.o t_trace.o - $(RM) t_expand_path.o + $(RM) t_expand_path.o t_ctxprf.o @libobj_frag@ diff --git a/src/lib/krb5/os/deps b/src/lib/krb5/os/deps index aed7792a7..9add6eca9 100644 --- a/src/lib/krb5/os/deps +++ b/src/lib/krb5/os/deps @@ -211,8 +211,7 @@ init_os_ctx.so init_os_ctx.po $(OUTPRE)init_os_ctx.$(OBJEXT): \ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ $(top_srcdir)/include/krb5/locate_plugin.h $(top_srcdir)/include/krb5/plugin.h \ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ - $(top_srcdir)/util/profile/prof_int.h init_os_ctx.c \ - os-proto.h + init_os_ctx.c os-proto.h krbfileio.so krbfileio.po $(OUTPRE)krbfileio.$(OBJEXT): \ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ @@ -519,6 +518,17 @@ write_msg.so write_msg.po $(OUTPRE)write_msg.$(OBJEXT): \ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/locate_plugin.h \ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ $(top_srcdir)/include/socket-utils.h os-proto.h write_msg.c +t_ctxprf.so t_ctxprf.po $(OUTPRE)t_ctxprf.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/locate_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h os-proto.h t_ctxprf.c t_expand_path.so t_expand_path.po $(OUTPRE)t_expand_path.$(OBJEXT): \ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ diff --git a/src/lib/krb5/os/init_os_ctx.c b/src/lib/krb5/os/init_os_ctx.c index 2be266f17..c35007888 100644 --- a/src/lib/krb5/os/init_os_ctx.c +++ b/src/lib/krb5/os/init_os_ctx.c @@ -29,7 +29,6 @@ #include "k5-int.h" #include "os-proto.h" #include "../krb/int-proto.h" -#include "prof_int.h" /* XXX for profile_copy, not public yet */ #if defined(_WIN32) #include @@ -468,7 +467,7 @@ krb5_set_config_files(krb5_context ctx, const char **filenames) return retval; if (ctx->profile) - profile_release(ctx->profile); + profile_abandon(ctx->profile); ctx->profile = profile; return 0; @@ -503,7 +502,7 @@ k5_os_free_context(krb5_context ctx) os_ctx->magic = 0; if (ctx->profile) { - profile_release(ctx->profile); + profile_abandon(ctx->profile); ctx->profile = 0; } diff --git a/src/lib/krb5/os/t_ctxprf.c b/src/lib/krb5/os/t_ctxprf.c new file mode 100644 index 000000000..c804d4896 --- /dev/null +++ b/src/lib/krb5/os/t_ctxprf.c @@ -0,0 +1,79 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/krb/t_ctxprf.c - krb5_init_context_profile() test */ +/* + * Copyright (C) 2024 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program tests the use of krb5_init_context_profile() with a modified + * profile object. If run with the single argument "empty", we create and + * modifiemodify an empty profile; otherwise, we retrieve and modify the + * profile for a normally created libkrb5 context. In both cases, we set a + * realm KDC value in the profile and use k5_locate_kdc() to verify that the + * setting is reflected in a libkrb5 context created using the modified + * profile. + */ + +#include "k5-int.h" +#include "os-proto.h" + +static void +check(long code) +{ + assert(code == 0); +} + +int +main(int argc, char **argv) +{ + profile_t p; + krb5_context ctx; + const char *names[] = { "realms", "KRBTEST.COM", "kdc", NULL }; + krb5_data realm = string2data("KRBTEST.COM"); + struct serverlist sl; + + if (argc > 1 && strcmp(argv[1], "empty") == 0) { + check(profile_init(NULL, &p)); + } else { + check(krb5_init_context(&ctx)); + check(krb5_get_profile(ctx, &p)); + krb5_free_context(ctx); + profile_clear_relation(p, names); + } + check(profile_add_relation(p, names, "ctx.prf.test")); + + check(krb5_init_context_profile(p, 0, &ctx)); + check(k5_locate_kdc(ctx, &realm, &sl, FALSE, FALSE)); + assert(sl.nservers == 1); + assert(strcmp(sl.servers[0].hostname, "ctx.prf.test") == 0); + + profile_abandon(p); + k5_free_serverlist(&sl); + krb5_free_context(ctx); +} diff --git a/src/lib/krb5/os/t_ctxprf.py b/src/lib/krb5/os/t_ctxprf.py new file mode 100644 index 000000000..1f3cc720d --- /dev/null +++ b/src/lib/krb5/os/t_ctxprf.py @@ -0,0 +1,15 @@ +from k5test import * + +realm = K5Realm(create_kdb=False) + +mark('initially empty profile') +realm.run(['./t_ctxprf', 'empty']) + +mark('modified single-file profile') +realm.run(['./t_ctxprf']) +with open(os.path.join(realm.testdir, 'krb5.conf')) as f: + contents = f.read() + if 'ctx.prf.test' in contents: + fail('profile changes unexpectedly flushed') + +success('krb5_init_context_profile() tests')