On Thu, 7 Mar 2024 at 20:50, James Carter <jwcart2@xxxxxxxxx> wrote: > > On Wed, Jan 31, 2024 at 8:42 AM Christian Göttsche > <cgzones@xxxxxxxxxxxxxx> wrote: > > > > Add a utility around selabel_cmp(3). > > > > Can be used by users to compare a pre-compiled fcontext file to an > > original text-based file context definition file. > > > > Can be used for development to verify compilation and parsing of the > > pre-compiled fcontext format works correctly. > > > > Signed-off-by: Christian Göttsche <cgzones@xxxxxxxxxxxxxx> > First of all many thanks for testing! > I don't have any comments on the code. > > It took me a while to get this to work. It was very confusing. Not > because of your code, but because of the behavior of selabel_cmp(). It > is not intuitive and I think that it needs to be better documented. It > will absolutely not work as expected on a copy of the contexts > directory. > > > With all that, not everything worked as I thought it would. > > current = installed contexts directory > the other file is by itself (and not in a copy of the contexts directory) > > 1) current file_contexts.bin vs file_contexts [Expected] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts.bin > file_contexts > spec /etc/selinux/targeted/contexts/files/file_contexts.bin is equal > to spec file_contexts > > 2) current file_contexts vs file_contexts [Not expected, diff says > they are equal] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts file_contexts > selabel_cmp: mismatch regex_str left remnant in stem tmp > selabel_cmp: mismatch child node tmp stem (root) > spec /etc/selinux/targeted/contexts/files/file_contexts is > uncomparable to spec file_contexts Since the database is opened via selabel_open(3) filecontexts.(homedirs|local) are implicitly loaded too. It would be confusing if the selabel_compare helper would deviate from that default behavior. Also currently there is no information in the in-memory database from which/how-many files the database was loaded. Maybe that should be added so the selabel_compare helper could give a hint? > 3) current file_contexts.bin vs file_contexts_1 [Expected] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts.bin > file_contexts_1 > spec /etc/selinux/targeted/contexts/files/file_contexts.bin is equal > to spec file_contexts_1 > > 4) file_contexts vs file_contexts_1 [Expected] > selabel_compare file_contexts file_contexts_1 > spec file_contexts is equal to spec file_contexts_1 > > > I realize now that selabel_cmp() can only find a superset or a subset > if the only difference occurs at the very end. The longer file must be > exactly like the shorter file except for the additional lines at the > end. Thanks, this is indeed a shortcoming in v2 (regex prefix length were not considered). Fixed in wip-v3: https://github.com/SELinuxProject/selinux/compare/f07e4c3b66416f6337d6a2ea1e0d69555f8704ad..090af7e6510edb90f4e7df55346e5023ac190b66 > I tried testing with the zebra module disabled, so it should have been > a subset of the current policy, but, of course, selabel_cmp() was not > sophisticated enough to say that. It just says that everything is > uncomparable. It would be nice if something said that. > > > The following worked only in the most trivial case where I could just > use diff instead. > contexts1 = copy of original contexts directory > contexts2 = copy of new contexts directory with new selabel db > > 1) current file_contexts vs contexts1 file_contexts [Expected, but Confusing] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts > contexts1/files/file_contexts > contexts1/files/file_contexts.bin: Old compiled fcontext format, skipping > contexts1/files/file_contexts.homedirs.bin: Old compiled fcontext > format, skipping > spec /etc/selinux/targeted/contexts/files/file_contexts is equal to > spec contexts1/files/file_contexts What is confusing here? > 2) current file_contexts.bin vs contexts1 file_contexts [Not expected. > Expected to be able to compare the current file_contexts.bin with old > text file_contexts] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts.bin > contexts1/files/file_contexts > contexts1/files/file_contexts.bin: Old compiled fcontext format, skipping > contexts1/files/file_contexts.homedirs.bin: Old compiled fcontext > format, skipping > selabel_cmp: mismatch regex_str right remnant in stem tmp > selabel_cmp: mismatch child node tmp stem (root) > spec /etc/selinux/targeted/contexts/files/file_contexts.bin is > uncomparable to spec contexts1/files/file_contexts Gives subset with v3 (since when using a .bin extension .homedirs and .local are not loaded). > 3) current file_contexts.bin vs contexts1 file_contexts.bin [Expected, > but confusing] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts.bin > contexts1/files/file_contexts.bin > contexts1/files/file_contexts.bin: Old compiled fcontext format, skipping > contexts1/files/file_contexts.bin: Old compiled fcontext format, skipping > ERROR: selabel_open - Could not obtain handle for > contexts1/files/file_contexts.bin: No such file or directory Updated errno to EINVAL for this case. > 4) current file_contexts vs contexts2 file_contexts [Expected] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts > contexts2/files/file_contexts > spec /etc/selinux/targeted/contexts/files/file_contexts is equal to > spec contexts2/files/file_contexts > > 5) current file_contexts.bin vs contexts2 file_contexts [Not expected. > Expected to be able to compare the current file_contexts.bin with > contexts2 text file_contexts] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts.bin > contexts2/files/file_contexts > selabel_cmp: mismatch regex_str right remnant in stem tmp > selabel_cmp: mismatch child node tmp stem (root) > spec /etc/selinux/targeted/contexts/files/file_contexts.bin is > uncomparable to spec contexts2/files/file_contexts Should give subset (due to the missing .homedirs|.local) now. > 6) current file_contexts.bin vs contexts2 file_contexts.bin [Expected] > selabel_compare /etc/selinux/targeted/contexts/files/file_contexts.bin > contexts2/files/file_contexts.bin > spec /etc/selinux/targeted/contexts/files/file_contexts.bin is equal > to spec contexts2/files/file_contexts.bin > > 7) contexts1 file_contexts vs contexts2 file_contexts [Expected, but confusing] > selabel_compare contexts1/files/file_contexts contexts2/files/file_contexts > contexts1/files/file_contexts.bin: Old compiled fcontext format, skipping > contexts1/files/file_contexts.homedirs.bin: Old compiled fcontext > format, skipping > spec contexts1/files/file_contexts is equal to spec > contexts2/files/file_contexts What is confusing here? selabel_open(3) tries to open binary fcontext files first for performance. > 8) contexts1 file_contexts vs contexts2 file_contexts.cpy (which is a > copy of file_contexts) [Not expected. Expected them to be equal] > selabel_compare contexts1/files/file_contexts contexts2/files/file_contexts.cpy > contexts1/files/file_contexts.bin: Old compiled fcontext format, skipping > contexts1/files/file_contexts.homedirs.bin: Old compiled fcontext > format, skipping > selabel_cmp: mismatch regex_str left remnant in stem tmp > selabel_cmp: mismatch child node tmp stem (root) > spec contexts1/files/file_contexts is uncomparable to spec > contexts2/files/file_contexts.cpy > > With all of the issues, I wonder how useful this actually would be. It > might cause more confusion then anything. > > Thanks, > Jim > > > --- > > v2: > > split nested block into own function > > --- > > libselinux/utils/.gitignore | 1 + > > libselinux/utils/selabel_compare.c | 122 +++++++++++++++++++++++++++++ > > 2 files changed, 123 insertions(+) > > create mode 100644 libselinux/utils/selabel_compare.c > > > > diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore > > index b3311360..2e10b14f 100644 > > --- a/libselinux/utils/.gitignore > > +++ b/libselinux/utils/.gitignore > > @@ -16,6 +16,7 @@ getseuser > > matchpathcon > > policyvers > > sefcontext_compile > > +selabel_compare > > selabel_digest > > selabel_get_digests_all_partial_matches > > selabel_lookup > > diff --git a/libselinux/utils/selabel_compare.c b/libselinux/utils/selabel_compare.c > > new file mode 100644 > > index 00000000..9ca6eff1 > > --- /dev/null > > +++ b/libselinux/utils/selabel_compare.c > > @@ -0,0 +1,122 @@ > > +#include <getopt.h> > > +#include <stdio.h> > > +#include <stdlib.h> > > +#include <string.h> > > + > > +#include <selinux/label.h> > > + > > + > > +static void usage(const char *progname) > > +{ > > + fprintf(stderr, > > + "usage: %s [-b backend] [-v] file1 file2\n\n" > > + "Where:\n\t" > > + "-b The backend - \"file\", \"media\", \"x\", \"db\" or \"prop\" (defaults to \"file\")\n\t" > > + "-v Validate entries against loaded policy.\n\t" > > + "file1/file2 Files containing the specs.\n", > > + progname); > > +} > > + > > +static int compare(const char *file1, const char *file2, const char *validate, unsigned int backend) > > +{ > > + struct selabel_handle *hnd1, *hnd2; > > + const struct selinux_opt selabel_option1[] = { > > + { SELABEL_OPT_PATH, file1 }, > > + { SELABEL_OPT_VALIDATE, validate } > > + }; > > + const struct selinux_opt selabel_option2[] = { > > + { SELABEL_OPT_PATH, file2 }, > > + { SELABEL_OPT_VALIDATE, validate } > > + }; > > + enum selabel_cmp_result result; > > + > > + hnd1 = selabel_open(backend, selabel_option1, 2); > > + if (!hnd1) { > > + fprintf(stderr, "ERROR: selabel_open - Could not obtain handle for %s: %m\n", file1); > > + return EXIT_FAILURE; > > + } > > + > > + hnd2 = selabel_open(backend, selabel_option2, 2); > > + if (!hnd2) { > > + fprintf(stderr, "ERROR: selabel_open - Could not obtain handle for %s: %m\n", file2); > > + selabel_close(hnd1); > > + return EXIT_FAILURE; > > + } > > + > > + result = selabel_cmp(hnd1, hnd2); > > + > > + selabel_close(hnd2); > > + selabel_close(hnd1); > > + > > + switch (result) { > > + case SELABEL_SUBSET: > > + printf("spec %s is a subset of spec %s\n", file1, file2); > > + break; > > + case SELABEL_EQUAL: > > + printf("spec %s is equal to spec %s\n", file1, file2); > > + break; > > + case SELABEL_SUPERSET: > > + printf("spec %s is a superset of spec %s\n", file1, file2); > > + break; > > + case SELABEL_INCOMPARABLE: > > + printf("spec %s is uncomparable to spec %s\n", file1, file2); > > + break; > > + default: > > + fprintf(stderr, "ERROR: selabel_cmp - Unexpected result %d\n", result); > > + return EXIT_FAILURE; > > + } > > + > > + return EXIT_SUCCESS; > > +} > > + > > +int main(int argc, char *argv[]) > > +{ > > + unsigned int backend = SELABEL_CTX_FILE; > > + int opt; > > + const char *validate = NULL, *file1 = NULL, *file2 = NULL; > > + > > + if (argc < 3) { > > + usage(argv[0]); > > + return EXIT_FAILURE; > > + } > > + > > + while ((opt = getopt(argc, argv, "b:v")) > 0) { > > + switch (opt) { > > + case 'b': > > + if (!strcasecmp(optarg, "file")) { > > + backend = SELABEL_CTX_FILE; > > + } else if (!strcmp(optarg, "media")) { > > + backend = SELABEL_CTX_MEDIA; > > + } else if (!strcmp(optarg, "x")) { > > + backend = SELABEL_CTX_X; > > + } else if (!strcmp(optarg, "db")) { > > + backend = SELABEL_CTX_DB; > > + } else if (!strcmp(optarg, "prop")) { > > + backend = SELABEL_CTX_ANDROID_PROP; > > + } else if (!strcmp(optarg, "service")) { > > + backend = SELABEL_CTX_ANDROID_SERVICE; > > + } else { > > + fprintf(stderr, "Unknown backend: %s\n", optarg); > > + usage(argv[0]); > > + return EXIT_FAILURE; > > + } > > + break; > > + case 'v': > > + validate = (char *)1; > > + break; > > + default: > > + usage(argv[0]); > > + return EXIT_FAILURE; > > + } > > + } > > + > > + if (argc != optind + 2) { > > + usage(argv[0]); > > + return EXIT_FAILURE; > > + } > > + > > + file1 = argv[optind++]; > > + file2 = argv[optind]; > > + > > + return compare(file1, file2, validate, backend); > > +} > > -- > > 2.43.0 > > > >