On Mon, 14 Oct 2024 15:36:02 +0200
Christian Franke wrote:
> Two possibly independent bugs found by 'stress-ng --lockf ...':
> 
> 1) lockf() may abort the process with api_fatal() if the new range 
> partly overlaps with two ranges previously locked by the same process.
> 
> 2) lockf() prints a warning on too many locks and returns success. It 
> should not print a warning and fail with ENOLCK instead.
> 
> Testcase for both:
> 
> $ uname -r
> 3.5.4-1.x86_64
> 
> $ cat locktest.c
> #include <fcntl.h>
> #include <stdio.h>
> #include <unistd.h>
> 
> static int lock_at(int fd, int off, int size)
> {
>    if (lseek(fd, off, SEEK_SET) < 0) {
>      perror("lseek"); return -1;
>    }
>    printf("\rlock %d-%d\n", off, off + size - 1); fflush(stdout);
>    if (lockf(fd, F_LOCK, size) < 0) {
>      perror("lock"); return -1;
>    }
>    return 0;
> }
> 
> int main(int argc, char **argv)
> {
>    int fd = open("locktest.tmp", O_RDWR|O_CREAT, 0666);
>    if (fd < 0) {
>      perror("open"); return 1;
>    }
> 
>    if (argc == 1) {
>      lock_at(fd, 0, 2);
>      lock_at(fd, 2, 2);
>      lock_at(fd, 1, 2);
>    }
>    else {
>      for (int i = 0; i < 914; i++)
>        if (lock_at(fd, i, 1))
>          return 1;
>    }
>    printf("\rdone\n");
>    return 0;
> }
> 
> $ gcc -o locktest locktest.c
> 
> $ ./locktest
> lock 0-1
> lock 2-3
> lock 1-2
>       1 [main] locktest 44864 C:\cygwin64\tmp\locktest.exe: \
>         *** fatal error - NtCreateEvent(lock): 0xC0000035\
>         Hangup
> 
> $ ./locktest loop
> lock 0-0
> lock 1-1
> lock 2-2
> lock 3-3
> ...
> lock 909-909
> lock 910-910
> lock 911-911
>        0 [main] locktest 44865 inode_t::get_all_locks_list: \
>          Warning, can't handle more than 910 locks per file.
> lock 912-912
>      727 [main] locktest 44865 inode_t::get_all_locks_list: \
>          Warning, can't handle more than 910 locks per file.
> lock 913-913
>     1329 [main] locktest 44865 inode_t::get_all_locks_list: \
>          Warning, can't handle more than 910 locks per file.
> done
> 
> There is possibly also an off-by-one error as the 912'th lockf() prints 
> the first warning.

Thanks for the report.
I looked into the problems, and considered how to fix them.

Could you please try the experimental patch attached?

-- 
Takashi Yano <takashi.y...@nifty.ne.jp>
diff --git a/winsup/cygwin/flock.cc b/winsup/cygwin/flock.cc
index 0f1efa01d..f99b7f538 100644
--- a/winsup/cygwin/flock.cc
+++ b/winsup/cygwin/flock.cc
@@ -297,6 +297,7 @@ class inode_t
     HANDLE              i_dir;
     HANDLE              i_mtx;
     uint32_t            i_cnt;    /* # of threads referencing this instance. */
+    uint32_t            lock_cnt;
 
   public:
     inode_t (dev_t dev, ino_t ino);
@@ -321,6 +322,7 @@ class inode_t
     void unlock_and_remove_if_unused ();
 
     lockf_t *get_all_locks_list ();
+    uint32_t get_lock_count () { return lock_cnt; };
 
     bool del_my_locks (long long id, HANDLE fhdl);
 };
@@ -503,7 +505,8 @@ inode_t::get (dev_t dev, ino_t ino, bool create_if_missing, 
bool lock)
 }
 
 inode_t::inode_t (dev_t dev, ino_t ino)
-: i_lockf (NULL), i_all_lf (NULL), i_dev (dev), i_ino (ino), i_cnt (0L)
+: i_lockf (NULL), i_all_lf (NULL), i_dev (dev), i_ino (ino), i_cnt (0L),
+  lock_cnt (0)
 {
   HANDLE parent_dir;
   WCHAR name[48];
@@ -586,6 +589,7 @@ inode_t::get_all_locks_list ()
   BOOLEAN restart = TRUE;
   bool last_run = false;
   lockf_t newlock, *lock = i_all_lf;
+  int cnt = 0;
 
   PDIRECTORY_BASIC_INFORMATION dbi_buf = (PDIRECTORY_BASIC_INFORMATION)
                                         tp.w_get ();
@@ -610,17 +614,16 @@ inode_t::get_all_locks_list ()
          dbi->ObjectName.Buffer[LOCK_OBJ_NAME_LEN] = L'\0';
          if (!newlock.from_obj_name (this, &i_all_lf, dbi->ObjectName.Buffer))
            continue;
-         if (lock - i_all_lf >= MAX_LOCKF_CNT)
-           {
-             system_printf ("Warning, can't handle more than %d locks per 
file.",
-                            MAX_LOCKF_CNT);
-             break;
-           }
+         if (lock - i_all_lf > MAX_LOCKF_CNT)
+           api_fatal ("Can't handle more than %d locks per file.",
+                      MAX_LOCKF_CNT);
          if (lock > i_all_lf)
            lock[-1].lf_next = lock;
          new (lock++) lockf_t (newlock);
+         cnt++;
        }
     }
+  lock_cnt = cnt;
   /* If no lock has been found, return NULL. */
   if (lock == i_all_lf)
     return NULL;
@@ -1349,6 +1352,10 @@ lf_setlock (lockf_t *lock, inode_t *node, lockf_t 
**clean, HANDLE fhdl)
   prev = head;
   block = *head;
   needtolink = 1;
+
+  if (node->get_lock_count () + 2 > MAX_LOCKF_CNT)
+    return ENOLCK;
+
   for (;;)
     {
       ovcase = lf_findoverlap (block, lock, SELF, &prev, &overlap);
@@ -1446,23 +1453,22 @@ lf_setlock (lockf_t *lock, inode_t *node, lockf_t 
**clean, HANDLE fhdl)
          prev = &lock->lf_next;
          lf_wakelock (overlap, fhdl);
          overlap->create_lock_obj ();
-         lock->create_lock_obj ();
-         needtolink = 0;
          continue;
 
        case 5: /* overlap ends after lock */
          /*
           * Add the new lock before overlap.
           */
-         if (needtolink) {
+         if (needtolink)
+           {
              *prev = lock;
              lock->lf_next = overlap;
-         }
+             needtolink = 0;
+           }
          overlap->lf_start = lock->lf_end + 1;
          lf_wakelock (overlap, fhdl);
-         lock->create_lock_obj ();
          overlap->create_lock_obj ();
-         break;
+         continue;
        }
       break;
     }
-- 
Problem reports:      https://cygwin.com/problems.html
FAQ:                  https://cygwin.com/faq/
Documentation:        https://cygwin.com/docs.html
Unsubscribe info:     https://cygwin.com/ml/#unsubscribe-simple

Reply via email to