This is an upstream bug, but I'm not subscribed to the upstream mailing list and really don't want another ML subscription. This function is defined thus: int matchpathcon_init_prefix(const char *path, const char *subset); matchpathcon_init_prefix() is the same as matchpathcon_init() but only loads entries with regular expressions that have stems prefixed by prefix. Apart from the obvious error (subset vs. prefix), the function doesn't actually seem to do that at all. First, once sefcontext_compile has run (well, we think that's the trigger), you can pass it any damn subset/prefix you please and it will load all contexts. I've been doing interactive tests with the Python module since it's easy, but per https://bugzilla.redhat.com/show_bug.cgi?id=1190377 and https://github.com/mlichvar/timedatex/pull/1 , the actual C library does seem to behave the same. So: [adamw@adam selinux (master)]$ ls /etc/selinux/targeted/contexts/files/*.bin /etc/selinux/targeted/contexts/files/file_contexts.bin /etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin /etc/selinux/targeted/contexts/files/file_contexts.local.bin [adamw@adam selinux (master)]$ python Python 2.7.9 (default, Apr 15 2015, 12:08:00) [GCC 5.0.0 20150319 (Red Hat 5.0.0-0.21)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import selinux >>> selinux.matchpathcon_init_prefix(None, '/foo/bar/monkeys') 0 >>> selinux.matchpathcon('/bin/bash', 0) [0, 'system_u:object_r:shell_exec_t:s0'] >>> selinux.matchpathcon_init_prefix(None, 'ilikehats') 0 >>> selinux.matchpathcon('/bin/bash', 0) [0, 'system_u:object_r:shell_exec_t:s0'] >>> If sefcontext_compile has *not* run - the case where we ran into the initial bug that led me to discover this stuff, that's #1190377 - it seems that the value you pass does have *some* significance, but it still doesn't work as advertised. To get a system in this situation for testing, you can install from a live image: until you update an selinux package or do something else that'd trigger an 'semodule' call or something, the system will be in this state. In this case, what happens seems to be that so long as the value you pass matches *any* of the regexps in one of the files in /etc/selinux/targeted/contexts/files , *all* of the entries in that file will be loaded. So if you pass a string that matches *any* of the regexps in /etc/selinux/targeted/contexts/files/file_contexts , *all* of the contexts in that file will be loaded - but not any of the ones in /etc/selinux/targeted/contexts/files/file_contexts.homedirs . If you pass a string that doesn't match any regexes in any of the files, no contexts will be loaded. So if we use /usr/lib/nagios/cgi/fooagsa (because it matches the regex /usr/lib/nagios/cgi(/.*)? ), we can then successfully do matchpathcon against a file that matches a completely *different* regex in that same file: >>> selinux.matchpathcon_init_prefix(None, '/usr/lib/nagios/cgi/fooagsa') 0 >>> selinux.matchpathcon('/bin/bash', 0) [0, 'system_u:object_r:shell_exec_t:s0'] But we can't do matchpathcon against /home/test: >>> selinux.matchpathcon('/home/test', 0) [0, 'system_u:object_r:default_t:s0'] because that's in file_contexts.homedirs. If we pass a string that just doesn't match any regex, all matchpathcons return default: >>> selinux.matchpathcon_init_prefix(None, 'ilikehats') 0 >>> selinux.matchpathcon('/home/adamw', 0) [0, 'system_u:object_r:default_t:s0'] >>> selinux.matchpathcon('/bin/bash', 0) [0, 'system_u:object_r:default_t:s0'] >>> This behaviour still doesn't match the documentation, to me. Note that fixing this might break users which have been using matchpathcon_init_prefix() wrongly but getting away with it because of the bug...like timedatex.
OK, now it's just being weird at me. This I *can* kinda understand on the basis that the entry for /etc/localtime is not a regex: >>> selinux.matchpathcon_init_prefix(None, '/etc/localtime') 0 >>> selinux.matchpathcon('/etc/localtime', 0) [0, 'system_u:object_r:default_t:s0'] >>> But this, I just can't come up with a convincing explanation for other than gremlins: >>> selinux.matchpathcon_init_prefix(None, '/etc/localtime') 0 >>> selinux.matchpathcon('/etc/resolv.conf', 0) [0, 'system_u:object_r:net_conf_t:s0'] >>> >>> selinux.matchpathcon_init_prefix(None, '/usr/share/zoneinfo') 0 >>> selinux.matchpathcon('/etc/resolv.conf', 0) [0, 'system_u:object_r:default_t:s0'] >>> selinux.matchpathcon('/etc/localtime', 0) [0, 'system_u:object_r:locale_t:s0'] >>> So...init_prefix /etc/localtime somehow loads the context for /etc/resolv.conf (but not /etc/localtime, as we saw above), while init_prefix /usr/share/zoneinfo loads the context for /etc/localtime (and lots of other things, like /bin/bash and so on) but somehow *not* the context for /etc/resolv.conf? What the hell.
Does seem to be a bug. However: 1) matchpathcon* are all deprecated interfaces, all users should have long since migrated to selabel*. 2) IIRC, matchpathcon_init_prefix() was purely added as an optimization for udev, so that it could prune loading of file_contexts entries to only /dev entries and speed up initialization. With the introduction of the precompiled file_contexts.bin file, this became entirely irrelevant.
Is there something unique about the python bindings? Because I see different results via python vs C when using matchpathcon_init_prefix: # rm -f /etc/selinux/targeted/contexts/files/*.bin # matchpathcon -p /etc /etc/localtime /etc/localtime system_u:object_r:locale_t:s0 # python Python 2.7.8 (default, Apr 15 2015, 09:26:43) [GCC 4.9.2 20150212 (Red Hat 4.9.2-6)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import selinux >>> selinux.matchpathcon_init_prefix(None, '/etc') 0 >>> selinux.matchpathcon('/etc/localtime', 0) [0, 'system_u:object_r:default_t:s0']
Well, I took a quick look and the bindings seemed very light. There's really only https://github.com/SELinuxProject/selinux/blob/master/libselinux/src/selinuxswig_python.i , isn't there? I can confirm the same thing you see, though. Odd. 'matchpathcon -p' seems to behave a lot more 'as expected' than the python case. *BUT*, when I tried patching timedatex to use matchpathcon_init_prefix(NULL, LOCALTIME_PATH) - which should be the same as matchpathcon -p /etc/localtime - it didn't work!
IMHO, we should just stop using matchpathcon_init_prefix() altogether; it was really only for udev and only ever called with a stem (e.g. "/dev"), not a full path. For that matter, the matchpathcon() family of functions was obsoleted by selabel* and users should be updated to use selabel* instead. matchpathcon utility might differ in another way though; it might be passing the file mode rather than 0 as the mode argument. Why that would make a difference though since the file_context's file does not specify a file mode for that entry I do not see...
Sure, we're already discussing exactly what timedatex should be doing instead of using matchpathcon* in the other bug. But even if timedatex stops using it, the odd behaviour of matchpathcon_init_prefix() doesn't go away, it still seems worth doing something about - even if that 'something' is to deprecate the function. There's definitely some kind of difference between command line 'matchpathcon -p' and C compiled matchpathcon_init_prefix(). timedatex git master as it stands actually winds up doing this: security_context_t con; matchpathcon_init_prefix(NULL, '../usr/share/zoneinfo/some/time/zone'); matchpathcon('/etc/localtime', 0, &con); lsetfilecon('/etc/localtime13236236', con); which is basically ridiculous, right? And it doesn't work. Obviously. But it *does* work if you do this - the only difference being to remove the '../' from the init_prefix call: security_context_t con; matchpathcon_init_prefix(NULL, '/usr/share/zoneinfo/some/time/zone'); matchpathcon('/etc/localtime', 0, &con); lsetfilecon('/etc/localtime13236236', con); doing that is enough to fix the initial bug here. Even though it looks completely wrong. You can do matchpathcon on /etc/localtime after doing matchpathcon_init_prefix on /usr/share/zoneinfo/blah/blah, and you get the correct value. It really really does work that way, I tested it several times. So, that's what first caused me to report this bug.
So I wrote a quick test program to demonstrate this bug, and another one I found: lgetfilecon() doesn't work if the .bin files haven't been created. Here it is: #include <selinux/selinux.h> #include <stdio.h> #define LOCALTIME_PATH "/etc/localtime" #define ZONEINFO_PATH "/usr/share/zoneinfo" void main() { security_context_t con; lgetfilecon(LOCALTIME_PATH, &con); printf("lgetfilecon: %s\n", con); matchpathcon_init_prefix(NULL, LOCALTIME_PATH); matchpathcon(LOCALTIME_PATH, 0, &con); printf("matchpathcon (%s): %s\n", LOCALTIME_PATH, con); matchpathcon_init_prefix(NULL, ZONEINFO_PATH); matchpathcon(LOCALTIME_PATH, 0, &con); printf("matchpathcon (%s): %s\n", ZONEINFO_PATH, con); } compile with 'gcc -lselinux'. On a system with the .bin files, you get: lgetfilecon: system_u:object_r:locale_t:s0 matchpathcon (/etc/localtime): system_u:object_r:locale_t:s0 matchpathcon (/usr/share/zoneinfo): system_u:object_r:locale_t:s0 on a system without the .bin files, you get: lgetfilecon: system_u:object_r:default_t:s0 matchpathcon (/etc/localtime): system_u:object_r:default_t:s0 matchpathcon (/usr/share/zoneinfo): system_u:object_r:locale_t:s0 But note, 'matchpathcon -p' does not behave the same on the no-.bin-files system: [test@localhost ~]$ matchpathcon -p /etc/localtime /etc/localtime /etc/localtime system_u:object_r:locale_t:s0 [test@localhost ~]$ matchpathcon -p /usr/share/zoneinfo /etc/localtime /etc/localtime system_u:object_r:default_t:s0
> lgetfilecon: system_u:object_r:default_t:s0 That can't be right. lgetfilecon just calls lgetxattr() on the file; it has nothing to do with file_contexts.bin or matchpathcon at all. I do see the difference in matchpathcon(/etc/localtime) output when I run it locally though. matchpathcon -p appears to be calling lstat() on the file and passing the mode to matchpathcon() rather than passing 0, but passing 0 is supposed to match any mode.
"That can't be right. lgetfilecon just calls lgetxattr() on the file; it has nothing to do with file_contexts.bin or matchpathcon at all." OK, so talking about the .bin files is kind of wrong. It's probably something *else* that usually happens but hasn't happened yet, right after you install from a live image. There's a bunch of detail to look into there and I don't know how far you want to get into it, but one simple element I can tell you is that selinux-policy has this: %triggerin -- pcre selinuxenabled && semodule -nB exit 0 %triggerin is interesting, but the upshot is that that *doesn't* run on live image creation (because selinux-policy gets installed before pcre) and hence when you install from the live image, it has not run, but it *does* run if you update or reinstall selinux-policy once you've installed the live image. Running semodule -nB produces the .bin files - but maybe it does something else too. Anyway, it's relatively easy to play with this if you want to - just grab an F22 live image, e.g. https://dl.fedoraproject.org/pub/alt/stage/22_TC3/Workstation/x86_64/iso/Fedora-Live-Workstation-x86_64-22-TC3.iso , install from it, and poke around. You obviously know selinux much better than me :), so maybe you can spot what else is different between the 'immediately post-live install' environment and a 'typical' environment that would change lgetfilecon's behaviour. But still, if you're interested in looking into that one, I guess I should file it separately, as it's clearly not the same thing as this.
Sorry, what I mean is that merely removing the .bin files won't change lgetfilecon() results, as those merely reflect what was actually set on the file. But, since removing the .bin files does change the matchpathcon() result, and since the program is setting the file context based on the matchpathcon() result, it makes sense that running lgetfilecon() after an install that did not include the .bin files would have a different result. Anyway, the issue is the difference in matchpathcon() result.
Added some instrumentation to libselinux: matchpathcon: Lookup /etc/localtime selabel_lookup_common: Lookup /usr/share/zoneinfo/America/New_York lookup_common: Lookup /usr/share/zoneinfo/America/New_York Matched /.* on /usr/share/zoneinfo/America/New_York (stem none) matchpathcon (/etc/localtime): system_u:object_r:default_t:s0 matchpathcon: Lookup /etc/localtime selabel_lookup_common: Lookup /usr/share/zoneinfo/America/New_York lookup_common: Lookup /usr/share/zoneinfo/America/New_York Matched /usr/share/zoneinfo(/.*)? on /share/zoneinfo/America/New_York (stem /usr) matchpathcon (/usr/share/zoneinfo): system_u:object_r:locale_t:s0 At some point RH helpfully added code to libselinux matchpathcon() to call realpath() on the pathname? commit 7df397d3d916e7018981b9fcf8062f992b4cec49 Author: Eric Paris <eparis> Date: Wed Aug 17 11:24:25 2011 -0400 libselinux: move realpath helper to matchpathcon library Instead of only doing path simplification and symlink following for the matchpathcon helper instead do it in the library potion. This was an issue when in python some called selinux.matchpatchcon("//lib64", 0) and got the wrong answer (because the // wasn't being dealt with) Signed-off-by: Eric Paris <eparis> Acked-by: Dan Walsh <dwalsh>
oh, duh, yeah, the lgetfilecon() is just getting its actual context, not its expected context according to policy. silly me. Let's drop that out entirely. So, what you're saying is that the matchpathcon() call resolves the symlink and tries to find the correct context for the target, right, rather than the correct context for the symlink itself? So that almost explains it, except check this: #include <selinux/selinux.h> #include <stdio.h> #define LOCALTIME_PATH "/etc/localtime" #define ZONEINFO_PATH "/usr/share/zoneinfo" #define SOME_OTHER_PATH "/usr/lib/nagios/cgi" void main() { security_context_t con; matchpathcon_init_prefix(NULL, LOCALTIME_PATH); matchpathcon(LOCALTIME_PATH, 0, &con); printf("matchpathcon (prefix %s): %s\n", LOCALTIME_PATH, con); matchpathcon_fini(); matchpathcon_init_prefix(NULL, ZONEINFO_PATH); matchpathcon(LOCALTIME_PATH, 0, &con); printf("matchpathcon (prefix %s): %s\n", ZONEINFO_PATH, con); matchpathcon_fini(); matchpathcon_init_prefix(NULL, SOME_OTHER_PATH); matchpathcon(LOCALTIME_PATH, 0, &con); printf("matchpathcon (prefix %s): %s\n", SOME_OTHER_PATH, con); matchpathcon_fini(); } [test@localhost ~]$ ./selinux_test matchpathcon (prefix /etc/localtime): system_u:object_r:default_t:s0 matchpathcon (prefix /usr/share/zoneinfo): system_u:object_r:locale_t:s0 matchpathcon (prefix /usr/lib/nagios/cgi): system_u:object_r:locale_t:s0 why does it work with the /usr/lib/nagios/cgi prefix? Note, however, it doesn't work if I use some path under /bin or /var. Is it that it loads all contexts that start with /usr , so when you ask it to matchpathcon /etc/localtime it resolves it to /usr/share/zoneinfo/(whatever) and it has that loaded? I admit I don't quite understand what the phrase "entries with regular expressions that have stems prefixed by prefix" is intended to mean, but it doesn't read like it can possibly mean "when the prefix is /usr/share/zoneinfo, load all entries that start with /usr".
That appears to be what it is doing. Remember that this was originally for udev to skip everything but /dev entries when loading file_contexts due to the cost of regex compiling all the file_contexts entries. Aside from being poorly documented (or poorly implemented, take your pick), matchpathcon_init_prefix() is obsolete given file_contexts.bin support; that avoids the overhead associated with loading all the file_contexts entries, particularly the regex compilation time. matchpathcon() should never have been changed to call realpath() internally IMHO. selabel_lookup() does not do that. Obviously the caller can do that if it is appropriate to do so, but it shouldn't be assumed that the caller wants to label the target and not the symlink itself. And all of matchpathcon*() was supposed to be obsoleted by selabel*().
OK, thanks. So at least we understand what's going on now. I guess it's up to you what the sensible thing to do in response to this in libselinux is :) We have some possible directions for timedatex to go in too in the other bug, so I think we're fine.
Also, it appears that the realpath() is not applied to any terminal symlink in the pathname if you pass in S_IFLNK as the mode argument to matchpathcon(). It seems that they assumed everyone would lstat() the file and pass in the mode or otherwise specify the actual mode rather than passing the mode 0 (match anything) value when they added this realpath() support. As noted elsewhere though, selinux.restorecon('/etc/localtime') would work after rename.
Aha - indeed, if I change the test program to pass S_IFLNK instead of 0, it behaves as I would have expected and as the command-line utility does.
Created attachment 1024252 [details] matchpathcon/selabel_file: Fix man pages Upstream commit 26e05da0fc2d0a4bd274320968a88f8acbb3b6a6. Just updates the man pages to match reality. Whether or not we can change the behavior to be saner while preserving compatibility is another question.
Looks good, except you might want to note that _init() apparently loads all contexts if the .bin files exist?
You don't think that's covered by "However, this optimization is no longer necessary due to the use of file_contexts.bin files with precompiled regular expressions, so use of this interface is deprecated."?
not entirely, it doesn't cover what actually happens if you *do* use it in that case. though it seems like a minor point, it seems unlikely that anyone's actually relying on some contexts *not* being loaded.
libselinux-2.3-10.fc22 has been submitted as an update for Fedora 22. https://admin.fedoraproject.org/updates/libselinux-2.3-10.fc22
libselinux-2.3-10.fc21 has been submitted as an update for Fedora 21. https://admin.fedoraproject.org/updates/libselinux-2.3-10.fc21
Package libselinux-2.3-10.fc22: * should fix your issue, * was pushed to the Fedora 22 testing repository, * should be available at your local mirror within two days. Update it with: # su -c 'yum update --enablerepo=updates-testing libselinux-2.3-10.fc22' as soon as you are able to. Please go to the following url: https://admin.fedoraproject.org/updates/FEDORA-2015-8872/libselinux-2.3-10.fc22 then log in and leave karma (feedback).
libselinux-2.3-10.fc22 has been pushed to the Fedora 22 stable repository. If problems still persist, please make note of it in this bug report.
libselinux-2.3-10.fc21 has been pushed to the Fedora 21 stable repository. If problems still persist, please make note of it in this bug report.