Bash – Potential workaround to inotifywait can’t produce NUL-delimited output

bashcfilenamesinotifySecurityshell-script

I'm currently writing a bash script that uses inotifywait to perform certain actions on a user-supplied list of files and directories.

It has come to my attention that unlike a lot of shell tools, inotifywait is unable to separate output records with \0. This leaves the possibility of injection attacks with specifically crafted, but legal filenames (containing newlines).

I would like to work around this to ensure my script does not introduce any unnecessary vulnerabilities. My approach is as folllows:

  • Ensure all files/paths passed for inotifywait to watch have trailing backslashes removed
  • Format inotifywait output with --format "%e %w%f//" to produce output as follows:
    • <EVENT LIST> <FILE PATH>//
  • Pipe inotifywait output to sed; any // found at the ends of lines with \0
  • Use bash while read loop to read \0-separated records
  • This means after the first record, all following records will have an extra leading newline. This is stripped off
  • Each record may then be split at the first space – before the space is the event list (comma separated as per inotifywait) – and after the space the full pathname associated with the event
#!/bin/bash

shopt -s extglob
watchlist=("${@}")

# Remove trailing slashes from any watchlist elements
watchlist=("${watchlist[@]%%+(/)}")

# Reduce multiple consecutive slashes to singles as per @meuh
watchlist=("${watchlist[@]//+(\/)/\/}")

printf -vnewline "\n"

inotifywait -qrm "${watchlist[@]}" --format "%e %w%f//" | \
    sed -u 's%//$%\x00%' | \
    while IFS= read -r -d '' line; do
        line="${line#${newline}}"
        events="${line%% *}"
        filepath="${line#* }"
        printf "events=%s\nfilepath=%q\n" "$events" "$filepath"
    done

As far as I can tell, this handles file/path names containing funny characters – spaces, newlines, quotes, etc. But it seems like a rather inelegant kludge.

For the purposes of this question, the ${watchlist[]} array is just copied from command-line parameters, but this array may be build otherwise and may contain "funny" characters.

  1. Are there any malicious paths that could break this? i.e. make the contents of the $events and $filepath variables be incorrect for any given event?

  2. If this is water-tight, is there any cleaner way to do this?

Note I know I could easily write a program to call inotify_add_watch() and friends to get around this. But for now due to other dependencies I am working in bash.


I've been conflicted on whether to post this here or codereview.SE or even the main so.SE.

Best Answer

You would need to sanitize watchlist to replace any // with /. Consider a directory named \nabc (where \n is a newline):

$ mkdir t
$ mkdir t/$'\nabc'
$ touch t/$'\nabc'/x

If passed the directory t//$'\nabc' you will see output with bogus // at the end of lines:

$ inotifywait -m -r t//$'\nabc' --format "%e %w%f//" 
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
OPEN t//
abc/x//
ATTRIB t//
abc/x//
CLOSE_WRITE,CLOSE t//
abc/x//

Note, you could also use -c instead of --format to get csv style output, which double-quotes filenames with newlines, but it is harder to parse, and in my case core dumps on the above example.

Example output for -c and touch t/$'new\nfile':

t/,CREATE,"new
file"
Related Question