Skip to content

Commit de266a2

Browse files
committed
MGM: Make "move directory into subdirectory of itself" protection robust
Fixes EOS-2850.
1 parent f70f2c2 commit de266a2

File tree

6 files changed

+110
-0
lines changed

6 files changed

+110
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ kineticio-dist.tgz
4141
/.settings/
4242
.cproject
4343
.project
44+
.vscode

.ignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ common/fmt/
33
namespace/ns_quarkdb/qclient/src/fmt/
44
common/sqlite/
55
man/man1/
6+
.vscode

mgm/XrdMgmOfs.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "namespace/Constants.hh"
3737
#include "namespace/interface/ContainerIterators.hh"
3838
#include "namespace/utils/Checksum.hh"
39+
#include "namespace/utils/RenameSafetyCheck.hh"
3940
#include "authz/XrdCapability.hh"
4041
#include "mgm/Stat.hh"
4142
#include "mgm/Access.hh"

mgm/XrdMgmOfs/Rename.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,11 @@ XrdMgmOfs::_rename(const char* old_name,
424424
if (renameDir) {
425425
rdir = dir->findContainer(oPath.GetName());
426426

427+
if(!eos::isSafeToRename(gOFS->eosView, rdir.get(), newdir.get())) {
428+
errno = EINVAL;
429+
return Emsg(epname, error, EINVAL, "rename - old path is subpath of new path");
430+
}
431+
427432
if (rdir) {
428433
// Remove all the quota from the source node and add to the target node
429434
std::map<std::string, std::set<std::string> >::const_reverse_iterator rfoundit;
@@ -574,6 +579,13 @@ XrdMgmOfs::_rename(const char* old_name,
574579
gOFS->FuseXCastContainer(rdir->getIdentifier());
575580
gOFS->FuseXCastContainer(rdir->getParentIdentifier());
576581
} else {
582+
// Do the check once again, because we're paranoid
583+
if(!eos::isSafeToRename(gOFS->eosView, rdir.get(), newdir.get())) {
584+
eos_static_crit("%s", SSTR("Unsafe rename of container " << rdir->getId() << " -> " << newdir->getId() << " was prevented at the last resort check"));
585+
errno = EINVAL;
586+
return Emsg(epname, error, EINVAL, "rename - old path is subpath of new path - caught by last resort check, quotanodes may have become inconsistent");
587+
}
588+
577589
// Remove from one container to another one
578590
unsigned long long tree_size = rdir->getTreeSize();
579591
{

namespace/ns_quarkdb/tests/HierarchicalViewTest.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "namespace/utils/TestHelpers.hh"
3030
#include "namespace/utils/RmrfHelper.hh"
3131
#include "namespace/Resolver.hh"
32+
#include "namespace/utils/RenameSafetyCheck.hh"
3233
#include <algorithm>
3334
#include <cstdint>
3435
#include <memory>
@@ -515,3 +516,16 @@ TEST_F(HierarchicalViewF, LostContainerTest)
515516
// TODO(gbitzes): Something wrong is here, this should succeed, investigate.
516517
// eos::RmrfHelper::nukeDirectory(view(), "/test/");
517518
}
519+
520+
TEST_F(HierarchicalViewF, RenameDirectoryAsSubdirOfItself)
521+
{
522+
std::shared_ptr<eos::IContainerMD> cont1 = view()->createContainer("/eos/dev/my-dir", true);
523+
std::shared_ptr<eos::IContainerMD> cont2 = view()->createContainer("/eos/dev/my-dir/subdir1", true);
524+
std::shared_ptr<eos::IContainerMD> cont3 = view()->createContainer("/eos/dev/my-dir/subdir1/subdir2", true);
525+
526+
ASSERT_TRUE(eos::isSafeToRename(view(), cont3.get(), cont1.get()));
527+
ASSERT_FALSE(eos::isSafeToRename(view(), cont1.get(), cont3.get()));
528+
529+
ASSERT_TRUE(eos::isSafeToRename(view(), cont2.get(), cont1.get())); // non-sensical to do, but safe (no-op)
530+
ASSERT_FALSE(eos::isSafeToRename(view(), cont1.get(), cont2.get()));
531+
}

namespace/utils/RenameSafetyCheck.hh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/************************************************************************
2+
* EOS - the CERN Disk Storage System *
3+
* Copyright (C) 2018 CERN/Switzerland *
4+
* *
5+
* This program is free software: you can redistribute it and/or modify *
6+
* it under the terms of the GNU General Public License as published by *
7+
* the Free Software Foundation, either version 3 of the License, or *
8+
* (at your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, *
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13+
* GNU General Public License for more details. *
14+
* *
15+
* You should have received a copy of the GNU General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
17+
************************************************************************/
18+
19+
//------------------------------------------------------------------------------
20+
//! @author Georgios Bitzes <[email protected]>
21+
//! @brief Helper function to check if it's safe to rename a directory into
22+
//! another
23+
//------------------------------------------------------------------------------
24+
25+
#ifndef EOS_NS_RENAME_SAFETY_CHECK_HH
26+
#define EOS_NS_RENAME_SAFETY_CHECK_HH
27+
28+
#include <iostream>
29+
#include "namespace/interface/IContainerMD.hh"
30+
#include "namespace/interface/IView.hh"
31+
#include "namespace/Namespace.hh"
32+
#include "common/Logging.hh"
33+
34+
EOSNSNAMESPACE_BEGIN
35+
36+
//------------------------------------------------------------------------------
37+
// Is it safe to make "source" directory a subdirectory of "target"?
38+
// Assumes eosViewRWMutex is at-least read-locked when calling this function.
39+
//------------------------------------------------------------------------------
40+
bool isSafeToRename(IView *view, IContainerMD *source, IContainerMD *target) {
41+
if(source == target) return false;
42+
43+
IContainerMDSvc *svc = view->getContainerMDSvc();
44+
IContainerMDPtr current = svc->getContainerMD(target->getParentId());
45+
46+
size_t iterations = 0;
47+
while(true) {
48+
iterations++;
49+
50+
if(iterations > 1024) {
51+
std::string msg = SSTR("potential loop when scanning parents of container "
52+
<< target->getId() << " - serious namespace corruption");
53+
54+
eos_static_crit("%s", msg.c_str());
55+
throw_mdexception(EFAULT, msg);
56+
}
57+
58+
if(current.get() == source) {
59+
return false; // Nope, sound alarm, this rename is not safe
60+
}
61+
62+
if(current->getId() == source->getId()) {
63+
// Should not happen.
64+
eos_static_crit("%s", SSTR("Two containers with the same ID ended up with different objects in memory - " <<
65+
current->getId() << " == " << source->getId() << " - " << current << " vs " << source));
66+
return false;
67+
}
68+
69+
if(current->getId() == 1) {
70+
// We've reached root, this rename looks safe.
71+
return true;
72+
}
73+
74+
// Move up one step.
75+
current = svc->getContainerMD(current->getParentId());
76+
}
77+
}
78+
79+
EOSNSNAMESPACE_END
80+
81+
#endif

0 commit comments

Comments
 (0)