rm -r long_path

Version:

coreutils-6.10 (fixed in 6.11)

How it is diagnosed (reproduced or source analysis)?

Reproduced and source analysis

How to reproduce?

1. mkdir ./test

2. create a long directory hierarchy (longer than 4096):

you can use the following perl script for this:

#!/usr/bin/perl

$dirname = "aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccdddddddddddddddddd";

for ($i = 0; $i < 50; $i++)

{

 $cmd = "mkdir ".$dirname;

 print $cmd."\n";

 system ($cmd);

 chdir $dirname;

 

}

3. $ coreutils-6.10/src/rm -r ./test

Symptom:

Rejecting correct input.

rm -r ./test mistakenly reports an error of descending into write-protected directory:

/rm: descend into write-protected directory `./test/aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccdddddd....?

The correct behavior to simply remove it.

Root cause:

The bug is in the function below. Comments written by us are in blue color.

code:

/* Turn -1 if FILE is unwritable.

  0 if it is writable.

  However, the logic is messed up when the path name is too long:

  they returned “-1” when such a long path is writable, causing

  the caller to think the file is write-protected...

*/

write_protected_non_symlink () {

.. .. ..

/* The bug occurs below. file_name_len here will be greater than

* MIN(PATH_MAX, 8192).

*

* euidaccess_stat (buf, W_OK) will return true if the current user

*   has ‘Write’ permission on the file. In this case, it will return

*   true since the user HAS write access. However, the logic is

*   wrong: they should have returned 0 when euidacess_stat returns

*   true, but instead, write_protected_non_symlink returned -1.

*

* The fix is simply reverse this logic.

*/

  if (MIN (PATH_MAX, 8192) <= file_name_len)

+        return ! euidaccess_stat (buf, W_OK);

-     return - euidaccess_stat (buf, W_OK);

.. .. ..

}

/* The caller of write_protected_non_symlink, prompt, will

* ask the user whether to descend into write-protected dir or not

*/

prompt (....) {

 if (...)

        write_protected =

         write_protected_non_symlink (fd_cwd, filename, ds, sbuf);

 if (write_protected || x->interactive == RMI_ALWAYS) {

         // Here is the FAILURE!!

      fprintf (stderr,  (write_protected

          ? _("%s: descend into write-protected directory %s? ")

          : _("%s: descend into directory %s? ")),  program_name, quoted_name);

  }

 return RM_OK;

} // end prompt

Is there Error Message?

Yes. When they detected that the dir is write-protected. However, it is arguable whether this message is an error message as its goal is to ask for user permission... Also, this msg is very misleading since it might lead programmers to believe that there is something wrong with the permission...

Can Errlog print an error message?

Yes, using the pattern of “untested branch decision”. The error branch decision below:

  if (MIN (PATH_MAX, 8192) <= file_name_len)

+        return ! euidaccess_stat (buf, W_OK);

-     return - euidaccess_stat (buf, W_OK);

the ‘true’ branch of the above ‘if’ is not tested at all! (otherwise they would have detected the bug during inhouse testing). Errlog can therefore print a warning msg to clearly pinpoint the root cause! This message is more useful to diagnose this bug than the one printed by the program itself (asking users for write permission).