LINUX.ORG.RU

Си: проверить права на файл/директорию для определенного пользователя


0

1

Есть путь к файлу и uid пользователя. Нужно проверить, имеет ли пользователь права на запись в файл, учитывая acl. Что-то типа access(), но чтобы проверяло для заданного uid. Можно ли использовать сочетание setfsuid()+access() в многопоточном приложении?

★★★

Похоже, только setuid из-под root + access. setfsuid во-первых, linux-specific, во-вторых, даже в его мане написано что использовать его не стоит.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

ok, а что с многопоточностью для «setuid из-под root + access»? Как это будет отрабатывать из разных потоков одного процесса?

conalex ★★★
() автор топика
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

int accessuid(const char *path, int mode, uid_t uid)
{
	pid_t ch;
	int fds[2];
	int rv = -1;
	int status = 0;

	if (uid == getuid()) {
		return access(path, mode);
	}

	if (pipe(fds) < 0) {
		return -1;
	}

	ch = fork();
	if (ch < 0) { /* Error */
		close(fds[0]);
		close(fds[1]);
		return -1;
	}

	if (ch > 0) { /* Parent: */
		close(fds[1]);
		if (read(fds[0], &rv, sizeof(int)) != sizeof(int)) {
			close(fds[0]);
			return -1;
		}
		close(fds[0]);
		if (waitpid(ch, &status, 0) < 0) {
			close(fds[0]);
			return -1;
		}
		return rv;
	} else { /* Child */
		close(fds[0]);
		if (setuid(uid) < 0) {
			rv = -1;
			write(fds[1], &rv, sizeof(int));
			exit(EXIT_FAILURE);
		}
		rv = access(path, mode);
		if (write(fds[1], &rv, sizeof(int)) != sizeof(int)) {
			exit(EXIT_FAILURE);
		}
		close(fds[0]);
		exit(EXIT_SUCCESS);
	}

	return -1;
}

int main(int argc, char *argv[])
{
	uid_t uid;
	const char *filename = NULL;
	int mode = R_OK;
	int i;

	if (argc < 3) {
		fprintf(stderr, "Usage: %s UID filename [mode]\n", argv[0]);
		return EXIT_FAILURE;
	}

	uid = atoi(argv[1]);
	filename = argv[2];
	if (argc > 3) {
		mode = 0;
		for (i = 0; argv[3][i]; i++) {
			switch (argv[3][i]) {
				case 'r':
					mode |= R_OK;
					break;
				case 'w':
					mode |= W_OK;
					break;
				case 'x':
					mode |= X_OK;
					break;
				default:
					break;
			}
		}
	}

	printf("ACCESS == %d\n", accessuid(filename, mode, uid));

	return EXIT_SUCCESS;
}

Я мог где-то, что-то не проверить, но идея такая. При fork'е остается рабочим только один поток, так что должно все отработать как надо. Еще лучше перед запуском потоков отфоркнуть маленького детеныша от рута и передавать ему запросы.

rymis ★★
()
Ответ на: комментарий от rymis

Вот мой велосипед (реализация access_uid() по алгоритму из книги «The Linux Programming Interface» Michael KerrisK, глава 17.2 «ACL Permission-Checking Algorithm»):


#include <stdlib.h>
#include <stdio.h>
#include <grp.h>
#include <pwd.h>
#include <acl/libacl.h>
#include <sys/acl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <unistd.h>

int
access_suid(const char* path, const char* user, mode_t mode)
{
  struct passwd *pw = getpwnam(user);
  if(pw == NULL)
    return false;
  if(setuid(pw->pw_uid) == -1)
    return false;
  int ret = access(path, mode) == 0;
  setuid(0);
  return ret;
}

int
access_uid(const char* path, const char* user, mode_t mode)
{
  int entryId;
  acl_permset_t permset;
  acl_t acl;
  acl_entry_t entry;
  acl_tag_t tag;
  uid_t *uidp;
  gid_t *gidp;
  struct stat sb;
  struct passwd *pw;
  int j, ngroups;
  gid_t *groups;
  mode_t uo_mode = 0;
  mode_t u_mode = 0;
  mode_t go_mode = 0;
  mode_t g_mode = 0;
  mode_t o_mode = 0;
  mode_t mask = 0;
  if(stat(path, &sb))
    return false;
  pw = getpwnam(user);
  if(pw == NULL)
    return false;
  ngroups = 0;
  getgrouplist(user, pw->pw_gid, groups, &ngroups);
  groups = (gid_t*) malloc(ngroups * sizeof (gid_t));
  if(groups == NULL)
    return false;
  if(getgrouplist(user, pw->pw_gid, groups, &ngroups) != -1)
    {
      acl = acl_get_file(path, ACL_TYPE_ACCESS);
      if(acl != NULL)
        {
          for(entryId = ACL_FIRST_ENTRY;; entryId = ACL_NEXT_ENTRY)
            {
              if(acl_get_entry(acl, entryId, &entry) != 1)
                break;
              if(acl_get_tag_type(entry, &tag) == -1)
                break;
              if(acl_get_permset(entry, &permset) == -1)
                break;
              int p = ((mode_t*) permset)[0];
              if(tag == ACL_USER_OBJ && sb.st_uid == pw->pw_uid)
                {
                  uo_mode = p;
                  continue;
                }
              if(tag == ACL_USER)
                {
                  uidp = (uid_t*) acl_get_qualifier(entry);
                  if(uidp != NULL && *uidp == pw->pw_uid)
                    u_mode = p;
                  continue;
                }
              if(tag == ACL_GROUP_OBJ && sb.st_gid == pw->pw_gid)
                {
                  go_mode = p;
                  continue;
                }
              if(tag == ACL_GROUP)
                {
                  gidp = (gid_t*) acl_get_qualifier(entry);
                  if(gidp != NULL)
                    {
                      for(j = 0; j < ngroups; j++)
                        {
                          if(*gidp == groups[j])
                            g_mode = p;
                        }
                    }
                  continue;
                }
              if(tag == ACL_OTHER)
                {
                  o_mode = p;
                  continue;
                }
              if(tag == ACL_MASK)
                {
                  mask = p;
                  continue;
                }
            }
          acl_free(acl);
        }
      free(groups);
    }
  if(uo_mode)
    return uo_mode & mode;
  if(u_mode)
    return u_mode & mask & mode;
  if(go_mode)
    return go_mode & mask & mode;
  if(g_mode)
    return g_mode & mask & mode;
  return o_mode & mode;
}

int
main(int argc, char** argv)
{
  const char* uid;
  const char *filename = NULL;
  int mode = R_OK;
  int i;

  if(argc < 3)
    {
      fprintf(stderr, "Usage: %s  fileName userName [mode]\n", argv[0]);
      return EXIT_FAILURE;
    }

  uid = argv[2];
  filename = argv[1];
  if(argc > 3)
    {
      mode = 0;
      for(i = 0; argv[3][i]; i++)
        {
          switch(argv[3][i])
            {
            case 'r':
              mode |= R_OK;
              break;
            case 'w':
              mode |= W_OK;
              break;
            case 'x':
              mode |= X_OK;
              break;
            default:
              break;
            }
        }
    }

  printf("ACCESS_UID == %d\n", access_uid(filename, uid, mode));
  printf("ACCESS_SUID == %d\n", access_suid(filename, uid, mode));
  return EXIT_SUCCESS;  
}
Может кто-то проверить? По-идее результат ACCESS_UID и ACCESS_SUID должен быть одинаков, в любых ситуациях .что хотелось бы.

conalex ★★★
() автор топика
Ответ на: комментарий от conalex

Вот мой велосипед (реализация access_uid() по алгоритму из книги «The Linux Programming Interface» Michael KerrisK, глава 17.2 «ACL Permission-Checking Algorithm»):

Не стоит переизобретать код, который уже есть в ядре. Никто никогда не сможет тебе гарантировать, что проверка прав в ядре будет давать тот же результат, что и твой код.

Даже сейчас он неправильно работает. chattr +i и получаем:

ACCESS_UID == 2
ACCESS_SUID == 0
Делаем файл с a+rwx внутри директории с a-rwx и получаем то же самое...

Если в книге этот алгоритм предлагается для реализации в реальных проектах, не связанных с реализацией FS/VFS, и не в качестве примера того, как это может быть реализовано в операционной системе, то эту книгу следует немедленно выкинуть.

Deleted
()
Ответ на: комментарий от Deleted

И ещё gcc ругается:

$ g++ -Wall -Wextra -lacl -o test test2.c 
test2.c: In function ‘int access_uid(const char*, const char*, mode_t)’:
test2.c:50:51: warning: ‘groups’ may be used uninitialized in this function [-Wmaybe-uninitialized]
   getgrouplist(user, pw->pw_gid, groups, &ngroups);
                                                   ^

Deleted
()
Ответ на: комментарий от conalex

uid - свойство всего процесса, так что понятно как. По-хорошему, нужно делать fork.

slovazap ★★★★★
()
Ответ на: комментарий от rymis

Еще лучше перед запуском потоков отфоркнуть маленького детеныша от рута и передавать ему запросы.

Т.е. держать всю дорогу рядом форкнутый процесс, этакий сервачок, и просить его сделать setuid()+access()? Хм.. интересно. Надо попробовать.

conalex ★★★
() автор топика
Ответ на: комментарий от rymis

Попробовал с форкнутым детенышем, работающим в фоне.

Во-первых, нужно использовать seteuid(), не setuid(), иначе единожды обработав запрос, детеныш так и остается под юзером, на рута обратно не переключается.

Во-вторых, проблема с потоками остается. В приложении используется библиотека zmq, для единообразия связь с детенышем реализована ее же средствами через ipc-сокет. Так вот, опять имеем узкое место - разные потоки, выступая в роли клиентов детеныша, шлют ему запросы, но по-скольку детеныш держит единственный поток на прием и обработку сообщений, получается не айс. Вряд ли будет часто случаться одновременное обращение к детенышу, но все же не приятно.

Есть еще какие-нибудь идеи?

conalex ★★★
() автор топика
Ответ на: комментарий от conalex

Поэтому он у меня и отфоркивался, чтобы не надо было обратно получать рута. seteuid не в POSIX, но я не представляю современную систему его не поддерживающую. Странно, что потоки очень активно посылают запросы, тем более, что в man access написано, что его использование не рекомендовано и предлагается делать open, во избежание уязвимости. Может вам не так уж и нужен access?

rymis ★★
()
Ответ на: комментарий от conalex

Может всё-таки своим велосипедом сделать?
Я для самодельного однопоточного FTP-сервера, работающего под рутом, для перехода
в другой каталог написал специальную СИ-функцию-перминатор, с вызовом stat().
Не использовал acl (я и не знаю, что это таое). А выдача и прием самих
файлов и листингов каталога одноразовым fork-нутым процессом с дерутизацией.
Основной рабочий процесс чтоб мог создавать сокеты на привилегированном порту.
Пришлось в цикле проходить все элементы пути на права доступа.
Конечно, у меня задача попроще, и допускается отказ в обслуживании, но
не допускается доступ туда, куда не следует. Вот фрагмент проверки прав:

//--------------------- dir_perm --------------------------

// cur_dir -текущая директория
// dir     -целевая директория
// ftp     -содержит номера пользователя и группы

int dir_perm(const char *cur_dir, const char *dir, struct ftp *ftp)
{
// Цикл.
// pdir -очередной кусок пути.
            if(stat(pdir, &st) < 0) return(-1);
            if((st.st_mode & S_IFMT)!=S_IFDIR) return(-1);
            if(st.st_uid == ftp->uid){
                if(((st.st_mode & S_IXUSR)==0)||
                   ((st.st_mode & S_IRUSR)==0)) return(-1);
            }
            else if(st.st_gid == ftp->gid){
                if(((st.st_mode & S_IXGRP)==0)||
                   ((st.st_mode & S_IRGRP)==0)) return(-1);
            }
            else if(((st.st_mode & S_IXOTH)==0)||
                ((st.st_mode & S_IROTH)==0)) return(-1);
}
Если получше отработать логику проверки и подогнать под общепринятые правила,
или под конкретную систему, может и ничего?

oleg_2
()
Ответ на: комментарий от conalex

Так вот, опять имеем узкое место - разные потоки, выступая в роли клиентов детеныша, шлют ему запросы, но по-скольку детеныш держит единственный поток на прием и обработку сообщений, получается не айс.

Что мешает создать столько дочерних процессов, сколько нужно для решения твоей задачи?

Deleted
()
Ответ на: комментарий от Deleted

К каждому потоку в многопоточной программе подвесить по процессу
тоже как-то не очень весело. Есть же правила доступа, их не может
не быть, иначе получится игра без правил. Сложны ли эти правила,
можно ли их познать, и не слишком ли сложной окажется проверка, это
вопрос.
А если Вы настаиваете, если запрсы не слишком частые, тогда так:

int
access_suid_wr(const char* path, uid_t uid)
{
    int status, fd;
    pid_t pid;
    pid = fork();
    if(pid < 0) return(-1);
    if(pid == 0){
        if(setuid(uid) == -1) exit(1);
        fd = open(path, O_WRONLY);
        if(fd < 0) exit(1);
        close(fd);
        exit(0);
    }
    if(waitpid(pid, &status, 0) < 0) return(-1);
    if(status==0) return(0);
    return(-1);
}
Еще вызывает сомнение использовать в многопоточной программе это:
struct passwd *pw = getpwnam(user);
Структура статическая, не будет ли накладки. К каждому потоку будет
своя, или одна общая структура? Если сомнительно, то это нужно вынести
в fork-нутый процесс.

oleg_2
()
Ответ на: комментарий от oleg_2

И еще, только сейчас вспомнил, дерутизацию надо делать по-другому, так:

//---------------- deroot_ftp ----------------------------

int deroot_ftp(struct ftp *ftp)
{
    int k;
    uid_t  uid;
    gid_t  gr_list[2];

    if(ftp->user[0]==0) return(-1);
    uid=getuid();
    if(uid == 0){

        //--- "дерутизация" ---

        //--- группа ---
        k=setgroups(0, gr_list);
        if(k<0) return(-1);

        k=setgid(ftp->gid);
        if(k<0) return(-1);

        //--- пользователь ---
        k=setuid(ftp->uid);
        if(k<0) return(-1);
    }
    else{
        if(ftp->uid != uid) return(-1);
    }
    return(0);
}
Тут я обнулил список групп пользователя. Если же его надо использовать,
так и надо его перезарядить с root-а на user-а.

oleg_2
()
Ответ на: комментарий от oleg_2

К каждому потоку в многопоточной программе подвесить по процессу тоже как-то не очень весело.

Во-первых, кто вам сказал, что дочерних процессов должно быть столько же, сколько и потоков? Их может быть больше или меньше, в зависимости от предполагаемой нагрузки (которая скорее всего будет мзерной). Во-вторых, какая вам разница: процессы у вас или потоки?

Есть же правила доступа, их не может не быть, иначе получится игра без правил. Сложны ли эти правила, можно ли их познать, и не слишком ли сложной окажется проверка, это вопрос.

Это очень сложная тема. Кроме стандартных «ugo/rwx» есть ещё: ext* attributes, POSIX ACL, SELinux/AppArmor/etc., fuse, nfs, странные внутриядерные ФС...

Если нужно проверить доступность файла для конкретного пользователя, то нужно использовать access() или даже open() для большей надёжности.

Deleted
()
Ответ на: комментарий от Deleted

Пришел к более естественному решению: вместо пула потоков-обработчиков запросов из сети использовать пул процессов. Тем более, что ZeroMQ (как я раньше говорил, использую zmq) позволяет перейти к пулу процессов очень легко. http://stackoverflow.com/questions/16114948/zeromq-share-a-context-with-all-c...

Велосипеды для самообразования, все-таки. Не поспоришь.

conalex ★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.