Friday, November 7, 2014

Unix Prog: Mandatory Locking

1. Mandatory Locking

Mandatory locking causes the kernel to check every open, read, and write to verify that the calling process isn't violating a lock on the file being accessed.

In some operating system, mandatory locking is enabled for a particular file by turning on the set-group-ID bit and turning off the group-execute bit.(This is a very stupid way, OS just want to find a unique way, which is not used by any one to mark the file has mandatory locking, if group-execute bit is off, then the file can not be executed by any group number, and then set-group-ID bit is not making any sense, this is the way nobody want to use)

If the file is locked by another process:
1) for blocking descriptor, try to read the file having a read lock of another process, good to go. try to write the file having a read lock of another process, blocks. try to read the file having a write lock of another process, blocks. try to write the file having a write lock of another process, blocks.

2) for nonblocking descriptor, try to read the file having a read lock of another process, good to go. try to write the file having a read lock of another process, return EAGAIN. try to read the file having a write lock of another process, return EAGAIN. try to write the file having a write lock of another process, return EAGAIN.

Normally open system again succeeds on opening the file even if the file has mandatory lock. But if open is called with flag O_TRUNC or O_CREAT, it will return the EAGAIN immediately.

Some special examples of unix application if the file has mandatory locking:
1) "ed" can still write the file content! Because ed use unlink the remove the file and create the new file. unlink is not restricted by mandatory locking.
2) "vi" editor is never able to edit the file.
3) Shell's ">" and ">>" operators to overwrite or append to the file resulted in the error "cannot create".

Mandatory record locking can also be used by a malicious user to hold a read lock on a file that is publicly readable.

2. Example:
lock.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<errno.h>  
 #include<fcntl.h>  
 #include<sys/wait.h>  
   
 volatile sig_atomic_t sigflag;  
 sigset_t newmask, oldmask, zeromask;  
   
 #define read_lock(fd, offset, whence, len) \  
  lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))  
 #define write_lock(fd, offset, whence, len) \  
  lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))  
   
 void sig_usr(int signo)  
 {  
  sigflag = 1;  
 }  
   
 void TELL_WAIT(void)  
 {  
  // Setup the signal handler  
  if(signal(SIGUSR1, sig_usr) == SIG_ERR) {  
   printf("signal error!\n");  
   exit(5);  
  }  
   
  if(signal(SIGUSR2, sig_usr) == SIG_ERR) {  
   printf("signal error!\n");  
   exit(5);  
  }  
   
  // Make signal mask  
  sigemptyset(&zeromask);  
  sigemptyset(&newmask);  
  sigaddset(&newmask, SIGUSR1);  
  sigaddset(&newmask, SIGUSR2);  
   
  // Block the SIGUSR1, SIGUSR2  
  if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {  
   printf("sigprocmask error!\n");  
   exit(6);  
  }  
 }  
   
 void TELL_PARENT(pid_t pid)  
 {  
  kill(pid, SIGUSR2);  
 }  
   
 void WAIT_PARENT(void)  
 {  
  // Child process wait for the SIGUSR1 from the parent  
  while(sigflag == 0)  
   sigsuspend(&zeromask);  
  sigflag = 0;  
   
  // After receiving the SIGUSR1 from parent  
  if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {  
   printf("sigprocmask error!\n");  
   exit(6);  
  }  
 }  
   
 void TELL_CHILD(pid_t pid)  
 {  
  kill(pid, SIGUSR1);  
 }  
   
 void WAIT_CHILD(void)  
 {  
  // Parent process wait for the SIGUSR2 from the child  
  while(sigflag == 0)  
   sigsuspend(&zeromask);  
  sigflag = 0;  
   
  // After receiving the SIGUSR2 from child  
  if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {  
   printf("sigprocmask error!\n");  
   exit(6);  
  }  
 }  
   
 int lock_reg(int fd, int cmd, int type, off_t offset, int whence, int len)  
 {  
  struct flock lock;  
   
  lock.l_type = type;  
  lock.l_start = offset;  
  lock.l_whence = whence;  
  lock.l_len = len;  
   
  return(fcntl(fd, cmd, &lock));  
 }  
   
 int set_fl(int fd, int flag)  
 {  
  int val;  
  if((val == fcntl(fd, F_GETFL, 0)) < 0) {  
   printf("fcntl error!\n");  
   exit(12);  
  }  
   
  val |= flag;  
  if(fcntl(fd, F_SETFL, val) < 0) {  
   printf("fcntl error!\n");  
   exit(12);  
  }  
 }  
   
 int main(int argc, char* argv[])  
 {  
  int fd;  
  pid_t pid;  
  char buf[5];  
  struct stat statbuf;  
   
  // Open the file descriptor  
  if((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC)) < 0) {  
   printf("open error!\n");  
   exit(1);  
  }  
   
  // Write content to the file  
  if(write(fd, "abcdef", 6) != 6) {  
   printf("write error!\n");  
   exit(2);  
  }  
   
  // Get the file descriptor state  
  if(fstat(fd, &statbuf) < 0) {  
   printf("fstat error!\n");  
   exit(3);  
  }  
   
  // Turn off the group execute bit and turn on the set-group-id bit  
  // to enable the mandatory locking  
  if(fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) {  
   printf("fchmod error!\n");  
   exit(4);  
  }  
   
  TELL_WAIT();  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(7);  
  } else if(pid > 0) { // parent  
   if(write_lock(fd, 0, SEEK_SET, 0) < 0) {  
    printf("write_lock error!\n");  
    exit(8);  
   }  
   
   TELL_CHILD(pid);  
   
   if(waitpid(pid, NULL, 0) < 0) {  
    printf("waitpid error!\n");  
    exit(9);  
   }  
  } else { // child  
   WAIT_PARENT();  
   set_fl(fd, O_NONBLOCK);  
   
   if(read_lock(fd, 0, SEEK_SET, 0) != -1) {  
    printf("child: read_lock succeeded");  
    exit(10);  
   }  
   
   printf("read_lock of already-locked region returns %d\n", errno);  
   
   if(lseek(fd, 0, SEEK_SET) == -1) {  
    printf("lseek error!\n");  
    exit(11);  
   }  
   
   if(read(fd, buf, 2) < 0) {  
    printf("read failed(mandatory locking works)\n");  
   }  
   else {  
    printf("read ok(no mandatory locking), buf = %2.2s\n", buf);  
   }  
  }  
   
  exit(0);  
 }  

shell:
The output of above program indicates that there is no mandatory locking in current system. Right now it is advisory locking which means, it assumes all processes are "cooperative" to use read_lock/write_lock to acquire the lock firstly before doing the I/O.
 ubuntu@ip-172-31-23-227:~$ ./lock.out test  
 read_lock of already-locked region returns 11  
 read ok(no mandatory locking), buf = ab  

No comments:

Post a Comment