Free Wildebeest
Free Wildebeest

Reputation: 7562

How can I check if I have permissions to open a file without opening it on Linux in C?

I want to be able to check to see if a file could be opened on Linux (for read or for read and write). However I don't have control of the code which will be opening the file, so I can't do what I would normally do which is to open it and then handle the error.

I appreciate that there will always be race conditions on any check due to permissions changing after the call has returned but before the open call, but I'm trying to avoid some undesirable error logging from a library which I have no control over.

I'm aware of stat, but I'd prefer not to need to try to replicate the logic of checking user IDs and group IDs.

Upvotes: 4

Views: 2231

Answers (3)

Andrew Y
Andrew Y

Reputation: 5117

(edit: the reason for adding this was that with this approach you can ensure you can avoid the race conditions. That said, it is quite a tricky approach, so maybe just coping with potential race conditions is a better practical approach).

If your goal is to shield the code that you do not own from unhandled errors, using LD_PRELOAD to intercept the open call itself might be of use. An example of it with malloc is here: Overriding 'malloc' using the LD_PRELOAD mechanism

here my quick improvisation on how you could do it - basically an interceptor that will launch an interactive shell to you to correct the error.

WARNING: lots of open calls actually do fail for legit reasons, e.g. when the program is going over different directories in the path trying to find the file, so treat this code as an educational example only to be used with this example code - if you are any close to real world use, your code definitely will need to be smarter. With all this said, let's get to the meat.

First, the "offensive" program that you do not have the control over:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[]) {
  int res = 0;
  printf("About to try to open the file...\n");
  res = open("/tmp/unreadable", O_RDONLY);
  printf("The result after opening: %d\n", res);
  if (res < 0) {
    perror("Could not open, and here is what the errno says");
  } else {
    char buf[1024];
    int fd = res;
    res = read(fd, buf, sizeof(buf));
    printf("Read %d bytes, here are the first few:\n", res);
    buf[30] = 0;
    printf("%s\n", buf); 
    close(fd);
  }
}

Then the interceptor:

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>

#define __USE_GNU
#include <dlfcn.h>

static int (*real_open)(const char *pathname, int flags, ...)=NULL;

static void __open_trace_init(void)
{

    real_open = dlsym(RTLD_NEXT, "open");
    if (NULL == real_open) {
        fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
        return;
    }
}

int open(const char *pathname, int flags, ...)
{

    if(real_open==NULL)
        __open_trace_init();
    va_list va;
    int res = 0;

    do {
      if (flags & O_CREAT) { 
        int mode = 0;
        va_start(va, flags);
        mode = va_arg(va, int);
        va_end(va);
        fprintf(stderr, "open(%s, %x, %x) = ", pathname, flags, mode);
        res = real_open(pathname, flags, mode);
        fprintf(stderr, "%d\n", res);
      } else {
        fprintf(stderr, "open(%s, %x) = ", pathname, flags);
        res = real_open(pathname, flags);
        fprintf(stderr, "%d\n", res);
      }
      if (res < 0) {
        printf("The open has returned an error. Please correct and we retry.\n");
        system("/bin/sh");
      }
    } while (res < 0);
    return res;
}

And here is how it looks like when running:

ayourtch@ayourtch-lnx:~$ echo This is unreadable >/tmp/unreadable
ayourtch@ayourtch-lnx:~$ chmod 0 /tmp/unreadable 
ayourtch@ayourtch-lnx:~/misc/stackoverflow$ LD_PRELOAD=./intercept ./a.out 
About to try to open the file...
open(/tmp/unreadable, 0) = -1
The open has returned an error. Please correct and we retry.
open(/dev/tty, 802) = 3
open(/dev/tty, 802) = 3
open(/home/ayourtch/.bash_history, 0) = 3
open(/home/ayourtch/.bash_history, 0) = 3
open(/lib/terminfo/x/xterm, 0) = 3
open(/etc/inputrc, 0) = 3
sh-4.1$ ls -al /tmp/unreadable
---------- 1 ayourtch ayourtch 19 2011-10-18 13:03 /tmp/unreadable
sh-4.1$ chmod 444 /tmp/unreadable
sh-4.1$ exit
open(/home/ayourtch/.bash_history, 401) = 3
open(/home/ayourtch/.bash_history, 0) = 3
open(/home/ayourtch/.bash_history, 201) = 3
open(/tmp/unreadable, 0) = 3
The result after opening: 3
Read 19 bytes, here are the first few:
This is unreadable
�0
ayourtch@ayourtch-lnx:~/misc/stackoverflow$

By the way this example also exposes an obvious bug in the first "test" code - I should have checked that the number of the chars read was at least 30 and put the null char accordingly.

Anyway, that code is supposed to be buggy and outside of the control, so it is kind of good to have a bug in it - else you would not need to use this kind of hack :-)

Upvotes: 1

You can use:

access("filename", R_OK);

or

euidaccess("filename", R_OK);

To check if your UID or EUID have read access to a respective file. (UID and EUID will be different if your are running setuid)

Upvotes: 10

Free Wildebeest
Free Wildebeest

Reputation: 7562

Use euidaccess or access, although you almost certainly always want to use the former.

Upvotes: 1

Related Questions