3

I have a situation in which I intend to communicate with a service through a command interface made available via a UNIX-domain socket on the file system. I am able to successfully send it commands, but for a while sat perplexed as to why I could not receive any response to my queries.

As it turns out, the service did not have sufficient permissions to write to the address I (or the OS) provided for it. However, I realized that if I explicitly bind to an address on the file system then I could adjust the file permissions by leveraging chmod.

Something like:

int mySocket;
struct sockaddr_un local_addr; 

mySocket = socket(AF_UNIX, SOCK_DGRAM, 0);
local_addr.sun_family = AF_UNIX;
snprintf(local_addr.sun_path, 108  "/path/to/mySocket");

bind(mySocket, (struct sockaddr *) &local_addr, sizeof(struct sockaddr_un));
chmod("/path/to/mySocket", 777);

That is to say, without the final chmod step, the service is unable to write to mySocket because it does not have the appropriate write permissions. Obviously, this is an even harder problem to spot if one does not explicitly bind to a specific address, since the underlying OS will implicitly generate this socket for the user - but it still exists and still will have the same access problems.

My question, then, is with respect to this final step. Is there a way to allow the OS to implicitly generate the socket for my endpoint (i.e. the address to which the service will respond) but request that it be given certain permissions?


The Explanation

The reason this issue is becoming a problem is due to the requirement that other portions of the program be executed as root. As such, when I, as root, attempt to connect/send to the background service, the OS will implicitly create an address to which replies will be directed. However, this leads to the problem that my socket-file, whether implicit or created with bind, will have permissions like srw- --- ---, so the other endpoint can only reply if they, too, elevate themselves.

Thus, the problem goes away if I first bind and then chmod the permissions as I showed above.

alk
  • 69,737
  • 10
  • 105
  • 255
sherrellbc
  • 4,650
  • 9
  • 48
  • 77
  • Yes, I *think* but I haven't tried it. In the source for `unix_bind`, we see this: [`(SOCK_INODE(sock)->i_mode & ~current_umask()`](http://lxr.free-electrons.com/source/net/unix/af_unix.c#L898) so the `l_mode` member combined with the current umask defines the initial mode as passed to `unix_mknod`. Note this code is from Linux, so YMMV on other flavors. And `sock` there corresponds to your `mySocket`. – bishop Jul 24 '15 at 20:48
  • @bishop, it seems a strange move to first invert the current mask before ANDing it. What is the *current_umask* referring to anyway? The current mask of what? – sherrellbc Jul 24 '15 at 20:56
  • This question is weird. Sockets are inherently bi-directional communication channels, there is no "permissions" problem. You should be able to read from and write to the socket descriptor without having to think about permissions. Can you show the exact code where you create the sockets and connect them? – Filipe Gonçalves Jul 24 '15 at 22:40
  • 1
    Also, `sizeof(struct sockaddr_un)` is wrong in `bind(2)`. You should pass it the exact size of the contents stored in the socket, which would be `sizeof(local_addr)-sizeof(local_addr.sun_path)+strlen("/path/to/mySocket")` – Filipe Gonçalves Jul 24 '15 at 22:42
  • @sherrellbc Mask is the creating processes inherited umask. `man umask` should give more details, but inverting then `&` is appropriate for how it works. You might try setting your umask a little more open. – bishop Jul 25 '15 at 00:44
  • 2
    Possible duplicate: http://stackoverflow.com/questions/20171747/how-to-create-unix-domain-socket-with-a-specific-permissions?rq=1 – bishop Jul 25 '15 at 00:46
  • @FilipeGonçalves, the service's socket is made available on the file system already, so I am not generating the channel via a fork, or similar. If I do not `bind` on the client side I am still able to `send` to the server, but am not able to receive any response. Similarly, if I first `bind` on the client side I am still unable to receive any response. However, if I `bind` *and* `chmod` the generated socket file to allow either *group* or *everyone else* permissions then I begin receiving the responses. – sherrellbc Jul 25 '15 at 13:31
  • @FilipeGonçalves, It is worth noting that I am doing this as root and the process I am communicating with was not started as such. Consequentially, the access permissions for the socket file generated is something like `wrx------`, so only root processes have a chance of getting through. – sherrellbc Jul 25 '15 at 13:43
  • @FilipeGonçalves, why would you want to specify to `bind` a smaller length than the actual? The actual `sockaddr_un` structure is potentially larger than what you suggest. `sockaddr_un` has two members, one being `sun_family` and the other being a character array of static size 108. If you specify the length as you suggest then you are only passing the size as a function of the number of bytes consumed in the array, not the actual size of the array (i.e. 108 bytes)? – sherrellbc Jul 26 '15 at 17:36
  • 1
    @sherrellbc Actually it looks like the call to `bind(2)` is valid even if you specify `sizeof(struct sockaddr_un)` rather than the actual bytes consumed, but note that `getsockname(2)`, `getpeername(2)` and `accept(2)` return a length that counts only the number of bytes used in the struct (see `man 7 unix`, under *Address format*). So I'd say it's good practice if you follow the same pattern when binding (which is what the examples in *Advanced Programming in the UNIX Environment* show too). – Filipe Gonçalves Jul 26 '15 at 17:48
  • 1
    @sherrellbc Quoting from the manpage: *pathname: a UNIX domain socket can be bound to a null-terminated file system pathname using bind(2). When the address of the socket is returned by getsockname(2), getpeername(2), and accept(2), its length is offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1* – Filipe Gonçalves Jul 26 '15 at 17:49
  • `man 7 unix` mentions that sockets in the filesystem honor the permissions of the directory they are in. Have you checked to make sure that the current working directory permissions are not too restrictive? Other than that, I don't think there's much you can do besides creating a working directory for the sockets, setting its permissions beforehand, and *then* create the sockets inside. – Filipe Gonçalves Jul 27 '15 at 21:34

2 Answers2

4

Is there a way to allow the OS to implicitly generate the socket for my endpoint (i.e. the address to which the service will respond) but request that it be given certain permissions?

I solved this very problem once using two calls to umask().

Pseudo code:

current_mask = umask(umask_to_be_used_on_afunix_socket_file_system_entry_creation);
bind afunix socket here
umask(current_umask);
alk
  • 69,737
  • 10
  • 105
  • 255
0

[edited] My first guess would be to instead use a fifo, which lets you create file first and set its permissions. Also, if two users are to communicate through the fifo, it's best to use group-level read/write and have them in the same group.

$ mkfifo -m 660 fifo_name

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);

As sherrellbc mentions, this is unrelated to unix sockets. If you are forced into using sockets, why not drop permissions before creating it? This seems like an important step when running suid anyway.

struct sockaddr_un to_addr;
memset(&to_addr, 0, sizeof(struct sockaddr_un));
strncpy(to_addr.sun_path, "/path/to/socket", sizeof(to_addr.sun_path) - 1);
to_addr.sun_family = AF_UNIX;

/* ------------ change user ------------ */
if(setgid(new_gid) || setuid(new_uid))
    goto error;

handle_conn(&to_addr, sizeof(struct sockaddr_un));

int handle_conn(struct sockaddr *to_addr, socklen_t addrlen) {
    int to_sock;
    int ret;
    pid_t pid;
    if( (to_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
            goto error;
    }
    if(connect(to_sock, to_addr, addrlen)) {
            close(to_sock);
            goto error;
    }

    ...                               

    close(to_sock);
}
David M. Rogers
  • 383
  • 3
  • 11
  • Although I was not aware of this functionality, the background service offers its control interface on the file-system as a socket, not a fifo. So, those wishing to communicate with the service must create a unix-domain socket and then `connect`/`send` to the service's socket. I just did a quick bit of research and these two ipc mechanisms appear to be unrelated. – sherrellbc Jul 26 '15 at 16:55