8

Suppose I'm user ID 1000. I run a program inside a UID namespace, mapping UID 0 inside the namespace to UID 1000 outside the namespace. The program then attempts to change its own UID to 50, which doesn't have a corresponding UID outside the namespace.

Does the program successfully change its own (kernel) UID? If so, what does its UID appear to be from outside the namespace? Is it still 1000, or is it something else?

2 Answers 2

10

I (finally) tested it in Fedora Rawhide. It looks like my initial impression is correct: if you run seteuid(50) inside a namespace, but uid 50 inside the namespace doesn't correspond to a uid outside the namespace, then the call to seteuid simply fails.

In here, I'll use "kernel uid" to refer to a process's uid as seen by the kernel (and most processes outside of the user namespace), and "subjective uid" to refer to a process's uid as seen by itself (and other processes inside the namespace).

Test 1

First, let's see what happens when root calls seteuid(50). We open up Python, and see what its pid is:

[fedora@ip-0-0-0-0 ~]$ sudo python                             
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.getpid()                                                       
1081                                                                  

In a second terminal, we can see that python's kernel uid is, in fact, 0 (root):

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1081
dr-xr-xr-x. 9 root root 0 Feb  7 03:27 /proc/1081

And the subjective uid is, of course, 0 as well:

>>> os.geteuid()  
0                 

So now we change our uid to 50:

>>> os.seteuid(50)

In the second terminal, we see that the kernel uid has indeed changed to 50:

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1081
dr-xr-xr-x. 9 50 root 0 Feb  7 03:27 /proc/1081

Test 2

Okay, now let's use "unshare" to create a new user namespace.

[fedora@ip-0-0-0-0 ~]$ unshare --user python                   
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.getpid()                                                       
1084                                                                  

In the second terminal, we see that this process's kernel uid is 1000 (fedora):

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1084
dr-xr-xr-x. 9 fedora fedora 0 Feb  7 03:30 /proc/1084

But in the first terminal, we see that its subjective uid is 65534 (not mapped):

>>> os.geteuid()
65534           

So, in the second terminal, we give python a mapping:

[fedora@ip-0-0-0-0 ~]$ echo '0 1000 1' > /proc/1084/uid_map

In the first terminal, we see that python's subjective uid is now 0:

>>> os.geteuid()                     
0                                    

We try to set python's uid to 50, but this fails with "Invalid argument" (presumably EINVAL), because the subjective uid 50 has no mapping to a kernel uid:

>>> os.seteuid(50)                   
Traceback (most recent call last):   
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument 

In the second terminal, we see that the process's kernel uid is unchanged:

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1084
dr-xr-xr-x. 9 fedora fedora 0 Feb  7 03:30 /proc/1084

And in the first terminal, we see that the process's subjective uid is unchanged, too:

>>> os.geteuid()
0               

Test 3

Okay, what happens if we define a mapping containing multiple uids? According to the LWN.net article "Namespaces in operation, part 5: User namespaces", we need to run python after mapping the uids. (I tried this without mapping the uids before running python, and the seteuid operation gave "Operation not permitted".) So first, we run a shell in a second new user namespace:

[fedora@ip-0-0-0-0 ~]$ unshare --user  
id: cannot find name for user ID 65534        
id: cannot find name for group ID 65534       
id: cannot find name for user ID 65534        
[I have no name!@ip-10-239-133-144 ~]$ echo $$
1798                                          

Then, in the second terminal, we need to be root in order to define the mapping:

[root@ip-10-239-133-144 fedora]# echo '0 1000 2' > /proc/1798/uid_map

According to the whoami command, our subjective uid is root now, so let's open up Python and set our subjective uid to 1:

[I have no name!@ip-0-0-0-0 ~]$ whoami                         
root                                                                  
[I have no name!@ip-0-0-0-0 ~]$ python                         
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.seteuid(1)                                                     
>>> os.getpid()                                                       
1837                                                                  

In the second terminal, we see that our kernel uid is now 1001, as expected:

[root@ip-10-239-133-144 fedora]# ls -ld /proc/1837
dr-xr-xr-x. 9 1001 fedora 0 Feb  7 04:00 /proc/1837

Test 2 again?

In test 3, we needed to define the mapping before running Python in order for the call to seteuid() to succeed. In test 2, we didn't do this. So, the obvious question is, if we did define the mapping before running Python, would the call to seteuid() work? It turns out that the answer is no. First terminal:

[fedora@ip-0-0-0-0 ~]$ unshare --user  
id: cannot find name for user ID 65534        
id: cannot find name for group ID 65534       
id: cannot find name for user ID 65534        
[I have no name!@ip-0-0-0-0 ~]$ echo $$
1861                                          

Second terminal:

[fedora@ip-0-0-0-0 ~]$ echo '0 1000 1' > /proc/1861/uid_map

First terminal:

[I have no name!@ip-0-0-0-0 ~]$ whoami                         
root                                                                  
[I have no name!@ip-0-0-0-0 ~]$ python                         
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.geteuid()                                                      
0                                                                     
>>> os.seteuid(50)                                                    
Traceback (most recent call last):                                    
  File "<stdin>", line 1, in <module>                                 
OSError: [Errno 22] Invalid argument                                  

Conclusion

In order for a call to seteuid within a user namespace to succeed, the uid passed to seteuid must have a mapping outside the user namespace. Otherwise, the call will fail with EINVAL.

3

You need to define the translations after you've forked.

An excerpt from a fine article at LWN.net: "System calls that return process user and group IDs — for example, getuid() and getgid() — always return credentials as they appear inside the user namespace in which the calling process resides. If a user ID has no mapping inside the namespace, then system calls that return user IDs return the value defined in the file /proc/sys/kernel/overflowuid, which on a standard system defaults to the value 65534. Initially, a user namespace has no user ID mapping, so all user IDs inside the namespace map to this value. Likewise, a new user namespace has no mappings for group IDs, and all unmapped group IDs map to /proc/sys/kernel/overflowgid (which has the same default as overflowuid)."

Basically, once you've clone(2)'d off your progeny, you'll have the child's PID and be able to add to the tables in /proc/CHILDPID/uid-map and /proc/CHILDPID/gid-map to begin defining the translations.

As I understand the situation, if you run seteuid(50) inside your new namespace without a translation for it, you'll find that your euid is still 65534 until the translation is defined. As above, if there is no matching translation, overflowuid/overflowgid will be substituted.

The LWN.net article that explains most of the situation brilliantly is here: http://lwn.net/Articles/532593/

3
  • You say your euid will be "still 65534". Since the program is running as uid 1000, won't its euid be 1000 as seen from the outside, and 0 as seen from the inside? If I run seteuid(50), according to you, it sounds like my euid will then be 65534 as seen from the outside. Will my euid be 50 as seen from the inside, as I'd expect? Commented Jan 30, 2014 at 1:17
  • I'm not fluent in kernelese, but for what it's worth, I looked at the Linux source code, and it looks like "default to 65534" is only the behavior for mapping a uid from outside a namespace into it. If you're trying to map a uid from inside a namespace out of it, the operation simply fails with EINVAL. So this makes it look like seteuid(50) will fail, since uid 50 as seen from the inside has no mapping outside the namespace. Commented Jan 30, 2014 at 1:23
  • I finally had the opportunity to test things, and it looks like this answer is incorrect. (Sorry if I'm beating a dead horse here.) If you run seteuid(50) without a matching substitution, overflowuid will not be substituted; instead, the call will fail with EINVAL. Commented Feb 7, 2014 at 4:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .