Hi!

Recenty I've discovered possible crash in FSFS locking code.  If, for some
reason, 'write-lock' cannot be obtained for lock/unlock operation, the FSFS
will SEGFAULT.

This happens beacuse lb.infos field is getting initialized only in function
lock_body() (see the code below).  So, if svn_fs_fs__with_write_lock() fails
without actual invoking the lock_body(), lb.infos will be left uninitialized.

[[[
svn_error_t *
svn_fs_fs__lock(svn_fs_t *fs,

  ...

  struct lock_baton lb;

  ...

  lb.fs = fs;
  lb.targets = sorted_targets;
  lb.comment = comment;
  lb.is_dav_comment = is_dav_comment;
  lb.expiration_date = expiration_date;
  lb.steal_lock = steal_lock;
  lb.result_pool = result_pool;

  err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, scratch_pool);
  for (i = 0; i < lb.infos->nelts; ++i)
    {

  ...
]]]

The same thing with svn_fs_fs__unlock().

I've attached the patch with crashing test and simple fix for this issue.

Log message:
[[[
Fix possible crash in svn_fs_fs__lock() / svn_fs_fs__unlock().

* subversion/subversion/tests/libsvn_fs/locks-test.c
  (obtain_write_lock_failure_test): New; test for the issue.

* subversion/subversion/libsvn_fs_fs/lock.c
  (lock_body,
   svn_fs_fs__lock): Initialize the lb.infos field before calling to
                     svn_fs_fs__with_write_lock().
  (unlock_body,
   svn_fs_fs__unlock): Same.

Patch by: Sergey Raevskiy <sergey.raevskiy{_AT_}visualsvn.com>
]]]
Index: subversion/libsvn_fs_fs/lock.c
===================================================================
--- subversion/libsvn_fs_fs/lock.c      (revision 1657295)
+++ subversion/libsvn_fs_fs/lock.c      (working copy)
@@ -853,9 +853,6 @@ lock_body(void *baton, apr_pool_t *pool)
   int i, outstanding = 0;
   apr_pool_t *iterpool = svn_pool_create(pool);
 
-  lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
-                             sizeof(struct lock_info_t));
-
   /* Until we implement directory locks someday, we only allow locks
      on files or non-existent paths. */
   /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
@@ -1056,9 +1053,6 @@ unlock_body(void *baton, apr_pool_t *pool)
   int i, max_components = 0, outstanding = 0;
   apr_pool_t *iterpool = svn_pool_create(pool);
 
-  ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
-                             sizeof(struct unlock_info_t));
-
   SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
   SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
 
@@ -1240,6 +1234,8 @@ svn_fs_fs__lock(svn_fs_t *fs,
 
   lb.fs = fs;
   lb.targets = sorted_targets;
+  lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
+                            sizeof(struct lock_info_t));
   lb.comment = comment;
   lb.is_dav_comment = is_dav_comment;
   lb.expiration_date = expiration_date;
@@ -1330,6 +1326,8 @@ svn_fs_fs__unlock(svn_fs_t *fs,
 
   ub.fs = fs;
   ub.targets = sorted_targets;
+  ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
+                            sizeof(struct unlock_info_t));
   ub.skip_check = FALSE;
   ub.break_lock = break_lock;
   ub.result_pool = result_pool;
Index: subversion/tests/libsvn_fs/locks-test.c
===================================================================
--- subversion/tests/libsvn_fs/locks-test.c     (revision 1657295)
+++ subversion/tests/libsvn_fs/locks-test.c     (working copy)
@@ -1079,6 +1079,62 @@ lock_cb_error(const svn_test_opts_t *opts,
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+obtain_write_lock_failure_test(const svn_test_opts_t *opts,
+                               apr_pool_t *pool)
+{
+  svn_fs_t *fs;
+  const char *conflict;
+  svn_revnum_t newrev;
+  svn_fs_access_t *access;
+  svn_fs_lock_target_t *target;
+  struct lock_many_baton_t baton;
+  apr_hash_t *lock_paths, *unlock_paths;
+
+  /* The test makes sense only for FSFS. */
+  if (strcmp(opts->fs_type, SVN_FS_TYPE_FSFS) != 0)
+    return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
+                            "this will test FSFS repositories only");
+
+  SVN_ERR(create_greek_fs(&fs, &newrev, "obtain-write-lock-failure-test",
+                          opts, pool));
+  SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
+  SVN_ERR(svn_fs_set_access(fs, access));
+
+  /* Make a read only 'write-lock' file.  This prevents any write operations
+     from being executed. */
+  
SVN_ERR(svn_io_set_file_read_only("obtain-write-lock-failure-test/write-lock",
+                                    TRUE, pool));
+
+  baton.results = apr_hash_make(pool);
+  baton.pool = pool;
+  baton.count = 0;
+
+  /* Trying to lock some paths.  We don't really care about error; the test
+     shouldn't crash. */
+  target = svn_fs_lock_target_create(NULL, newrev, pool);
+  lock_paths = apr_hash_make(pool);
+  svn_hash_sets(lock_paths, "/iota", target);
+  svn_hash_sets(lock_paths, "/A/mu", target);
+
+  apr_hash_clear(baton.results);
+  SVN_TEST_ASSERT_ANY_ERROR(svn_fs_lock_many(fs, lock_paths, "comment", 0, 0, 
0,
+                                             lock_many_cb, &baton, pool, 
pool));
+
+  /* Trying to unlock some paths.  We don't really care about error; the test
+     shouldn't crash. */
+  unlock_paths = apr_hash_make(pool);
+  svn_hash_sets(unlock_paths, "/iota", "");
+  svn_hash_sets(unlock_paths, "/A/mu", "");
+
+  apr_hash_clear(baton.results);
+  SVN_TEST_ASSERT_ANY_ERROR(svn_fs_unlock_many(fs, unlock_paths, TRUE,
+                                               lock_many_cb, &baton, pool,
+                                               pool));
+
+  return SVN_NO_ERROR;
+}
+
 /* ------------------------------------------------------------------------ */
 
 /* The test table.  */
@@ -1114,6 +1170,8 @@ static struct svn_test_descriptor_t test_funcs[] =
                        "lock multiple paths"),
     SVN_TEST_OPTS_PASS(lock_cb_error,
                        "lock callback error"),
+    SVN_TEST_OPTS_PASS(obtain_write_lock_failure_test,
+                       "lock/unlock when 'write-lock' couldn't be obtained"),
     SVN_TEST_NULL
   };
 

Reply via email to