ScannedFile: Identify already scanned files by device and inode

Zeek scripts located on separate filesystems, but sharing the same inode
number leads to scripts not being loaded. The reason is that a `ScannedFile`
is only identified by `st_ino` which is not enough to uniquely identify a
file in a system.

This problem may be hit when `ZEEKPATH` points to separate filesystems and
two script files happen have the same `st_ino` value - definitely not very
likely, but possibly very confusing when it happens.

The following test case creates two zeek scripts on separate filesystems.
As the filesystems are freshly created and of the same type, the files will
(tested a few times with xfs/ext4) have the same `st_ino` values.

    #!/bin/bash
    ZEEKDIR=${ZEEKDIR:-/home/awelzel/projects/zeek}
    export ZEEKPATH=.:${ZEEKDIR}/build/scripts:${ZEEKDIR}/scripts

    cat << EOF > hello.zeek
    event zeek_init() {
        print("Hello, once or twice?");
    }
    EOF

    for i in 1 2 ; do
        dd if=/dev/urandom of=img${i} count=16 bs=1M 2>/dev/null
        sudo mkfs.xfs -q ./img${i}
        mkdir -p mount${i}
        sudo mount ./img${i} ./mount${i}
        sudo cp hello.zeek ./mount${i}/hello.zeek
    done

    ls ./mount*/*zeek
    stat -c "%n: device=%d inode=%i" ./mount*/hello.zeek

    ${ZEEKDIR}/build/src/zeek -b ./mount1/hello.zeek ./mount2/hello.zeek

    # Cleanup
    for i in 1 2 ; do
        sudo umount ./mount${i}
        rm -rfv ./img${i} ./mount${i}
        rm -rfv hello.zeek
    done

Before this patch, `Hello, once or twice?` is printed only once,
afterwards twice:

    $ sh testcase.sh
    [sudo] password for awelzel:
    ./mount1/hello.zeek  ./mount2/hello.zeek
    ./mount1/hello.zeek: device=1794 inode=6915
    ./mount2/hello.zeek: device=1795 inode=6915
    Hello, once or twice?
    Hello, once or twice?
This commit is contained in:
Arne Welzel 2020-02-23 17:06:52 +01:00
parent 48fba11c51
commit 4b4595f5db
2 changed files with 19 additions and 19 deletions

View file

@ -91,7 +91,7 @@ static string find_relative_script_file(const string& filename)
return find_script_file(filename, bro_path());
}
static ino_t get_inode_num(FILE* f, const string& path)
static std::pair<dev_t, ino_t> get_inode(FILE* f, const string& path)
{
struct stat b;
@ -99,20 +99,20 @@ static ino_t get_inode_num(FILE* f, const string& path)
reporter->FatalError("fstat of %s failed: %s\n", path.c_str(),
strerror(errno));
return b.st_ino;
return std::pair<dev_t, ino_t>{b.st_dev, b.st_ino};
}
static ino_t get_inode_num(const string& path)
static std::pair<dev_t, ino_t> get_inode(const string& path)
{
FILE* f = open_file(path);
if ( ! f )
reporter->FatalError("failed to open %s\n", path.c_str());
ino_t inum = get_inode_num(f, path);
auto inode = get_inode(f, path);
fclose(f);
return inum;
return inode;
}
class FileInfo {
@ -420,7 +420,8 @@ when return TOK_WHEN;
else
{
// All we have to do is pretend we've already scanned it.
ScannedFile sf(get_inode_num(path), file_stack.length(), path, true);
auto i = get_inode(path);
ScannedFile sf(i.first, i.second, file_stack.length(), path, true);
files_scanned.push_back(sf);
}
}
@ -587,13 +588,10 @@ YYLTYPE GetCurrentLocation()
return currloc;
}
static bool already_scanned(ino_t i)
static bool already_scanned(const std::pair<dev_t, ino_t>& i)
{
list<ScannedFile>::const_iterator it;
for ( it = files_scanned.begin(); it != files_scanned.end(); ++it )
if ( it->inode == i )
for ( auto it : files_scanned )
if ( it.dev == i.first && it.inode == i.second )
return true;
return false;
@ -601,7 +599,7 @@ static bool already_scanned(ino_t i)
static bool already_scanned(const string& path)
{
return already_scanned(get_inode_num(path));
return already_scanned(get_inode(path));
}
static int load_files(const char* orig_file)
@ -656,7 +654,7 @@ static int load_files(const char* orig_file)
reporter->FatalError("can't open %s", file_path.c_str());
}
ino_t i = get_inode_num(f, file_path);
auto i = get_inode(f, file_path);
if ( already_scanned(i) )
{
@ -666,7 +664,7 @@ static int load_files(const char* orig_file)
return 0;
}
ScannedFile sf(i, file_stack.length(), file_path);
ScannedFile sf(i.first, i.second, file_stack.length(), file_path);
files_scanned.push_back(sf);
if ( g_policy_debug && ! file_path.empty() )