Thanks again for the explanations, that's exactly what I needed to
manage to have a look at your proposed changes quickly :)

Svante Signell, le Thu 21 May 2015 10:56:24 +0200, a écrit :
> - there are two cosmetic changes (I know they should not be there, but
> for code unification/readability)
> 
> - struct netnode contains
>   int openmodes;                /* O_READ | O_WRITE | O_EXEC */
> therefore I chose to limit the openmodes.

This is not only purely cosmetic: it actually changes something in the
code.  This is a browntape fix.  That's typically where electrical
engineers and computer scientists can't agree: a browntape fix is no
good in computer science, while it can be completely reasonable for
electrical engineering :) Instead, I have commited a hard assertion:
callers of new_node and check_openmodes are supposed to be careful (this
is what the existing code does, except in one place).

> - the condition (nn->openmodes & ~newmodes) is wrong since this triggers
> on newmodes being complementary to nn->openmodes 

Not exactly: it triggers when both `nn->openmodes' has some flag
that `newmodes' doesn't have, and `newmodes' has some flag that
`nn->openmodes' doesn't have. I.e. `nn->file' is missing an openmode,
but `file' is missing one too, so we really need to create another
file_t, in order to have all the openmodes that we need.

> /* Intersecting sets.
>    We need yet another new peropen on this node.  */
> The correct condition for intersection is the and operation, i.e.

I think it's the comment which is not precise enough, it'd rather be
"Intersecting sets with no inclusion".

> one example being
> nn->openmodes = 1 = O_READ and
> newmodes = 3 = O_READ | O_WRITE,; the intersection being O_READ.

The goal of check_openmodes is not the intersection, but the union,
actually, see nn->openmodes | newmodes in the dir_lookup call.

> For the test case given before and the wrong condition triggers the
> following:
> ./my_fakeroot-hurd rpctrace make 2>&1 | tee rpctrace_nOK.out
> ...
>   9<--154(pid16637)->dir_lookup ("dev/null" 1 0)
> Intersecting sets
> newmodes=1, (nn->openmodes=2 & ~newmodes=37777777776) = 2
> (nn->openmodes & newmodes) = 0
> 
> file == MACH_PORT_NULL
> nn->file=62
> (nn->openmodes=2 | newmodes=1) = 3
> bad_retryname=NULL, file=60
>  = 0 1 ""    175<--183(pid16637)
> 
> with no bad effects but
> 
>   146<--178(pid16637)->dir_lookup ("gnatvsn_from/alloc.ali" 1 0)
> Intersecting sets
> newmodes=1, (nn->openmodes=2 & ~newmodes=37777777776) = 2
> (nn->openmodes & newmodes) = 0
> 
> file == MACH_PORT_NULL
> nn->file=84
> (nn->openmodes=2 | newmodes=1) = 3
> bad_retryname=NULL, file=0
>  = 0x4000000d (Permission denied) 
> 
> has, since the file returned is zero.

Yes, but the ground reason is not a bogus test: here, we previously had
write access to gnatvsn_from/alloc.ali (nn->openmodes = 2), but now we
want read access too (newmodes = 1), so we reopen it, in order to get
both. Yes, we want to have both. Using your patch would mean that we
lose the write access. The test is really right: we want to reopen the
file, with the union of the modes. And it's the reopen which fails.

> According to dir_lookup in fs.defs a file name of null is equivalent to
> a reopen of that file.
>           err = dir_lookup (nn->file, "", nn->openmodes | newmodes, 0,
>                             &bad_retry, bad_retryname, &file);

Yes, and *that* is what produces the permission denied error. In
the case of /dev/null, there is no error since /dev/null is always
world-readable/writable. In the case of gnatvsn_from/alloc.ali, it
depends on the mode of the underlying file. And then I realize what you
wrote earlier in your mail:

> - An openmode equal to zero should not be allowed, but seems to be
> unavoidable. A zero openmode would mean no read or write access
> according to fcntl.h:
> # define O_NORW         0       /* Open without R/W access.  */
> However, things seems to work, even with openmodes being zero.

Well, I guess this is where things don't actually work: an initial open
of gnatvsn_from/alloc.ali with modes = 0, and thus fakeroot creates the
underlying file with no access right, and then when we want to re-open
it we can't since it doesn't have the permissions.

So, in the end, although the effect happens in check_openmodes, the
bug is somewhere between the creation of gnatvsn_from/alloc.ali
and the reopen here.  In your testcase, it's tar x which creates
gnatvsn_from/alloc.ali, the testcase can be reduced to:

(cd dst/gnatvsn_to; tar xf ../../file.tar)
md5sum dst/gnatvsn_to/gnatvsn_from/alloc.ali

within the same fakeroot-hurd session. To create the file, tar does:

10<--67(pid3274)->dir_lookup ("gnatvsn_from/alloc.ali" 4194362 256) = 0 1 ""    
86<--89(pid3274)
86<--89(pid3274)->term_getctty () = 0xfffffed1 ((ipc/mig) bad request message 
ID)
86<--89(pid3274)->io_write ("V "GNAT Lib v4.9"\nA -nostdinc\nA -O2\nA -fPIC\nA 
-g\nA -mtune=generic\nA -march=i586\n" -1) = 0 2267
86<--89(pid3274)->file_utimes ({0 0} {1418859518 0}) = 0
86<--89(pid3274)->file_chown (1000 1000) = 0
86<--89(pid3274)->file_chmod (292) = 0

i.e. initially mode 0, and only after, chmods it to 0444. Err, that's
why the reopen with mode = 3 can't work of course!  So the bug is that
since we still have the write openmode from the tar process, we don't
want to chmod to something which is not writable. Bingo.

Could you try the attached patch? It makes your testcase work fine, but
perhaps it has other unexpected consequences.

Samuel
diff --git a/trans/fakeroot.c b/trans/fakeroot.c
index a223bc4..feaab15 100644
--- a/trans/fakeroot.c
+++ b/trans/fakeroot.c
@@ -525,14 +529,27 @@ real_from_fake_mode (mode_t mode)
 error_t
 netfs_attempt_chmod (struct iouser *cred, struct node *np, mode_t mode)
 {
+  struct netnode *nn;
+  mode_t real_mode;
+
   if ((mode & S_IFMT) == 0)
     mode |= np->nn_stat.st_mode & S_IFMT;
   if ((mode & S_IFMT) != (np->nn_stat.st_mode & S_IFMT))
     return EOPNOTSUPP;
 
+  /* Make sure that `check_openmodes' will still always be able to reopen it */
+  real_mode = mode;
+  nn = netfs_node_netnode (np);
+  if (nn->openmodes & O_READ)
+    real_mode |= S_IRUSR;
+  if (nn->openmodes & O_WRITE)
+    real_mode |= S_IWUSR;
+  if (nn->openmodes & O_EXEC)
+    real_mode |= S_IXUSR;
+
   /* We don't bother with error checking since the fake mode change should
      always succeed--worst case a later open will get EACCES.  */
-  (void) file_chmod (netfs_node_netnode (np)->file, mode);
+  (void) file_chmod (nn->file, real_mode);
   set_faked_attribute (np, FAKE_MODE);
   np->nn_stat.st_mode = mode;
   return 0;

Reply via email to