0

Looking at a bash-script that takes input from Git commit comment to update itself. Simplified:

script:

#!/bin/bash

comment=''

printf '%s\n' "$comment"

upgrade_script() { # Download latest: curl -o updated_script https://path/to/file

    # Get comment:
    new_comment="$(curl https://path/to/comment)"

    # Update comment='' with new_comment:
    sed -i "3,0 s/comment=''/comment='$new_comment'/" updated_script

}

Issue is if comment has characters that either breaks sed or mangles bash. E.g:

# that's all she wrote!  => comment='that's all she wrote!
# use /need/ over /want/ => s/comment=''/'use /need/ over /want'/'

and then of course with the potential for both malicious but also unintended things like:

# Remove tmp files by: ' rm -r *;' => comment='Remove tmp files by: ' rm -r *;''

Would this be enough to battle the issue?

Add this before the sed -i command:

new_comment=$(
    sed \
    -e "s/'/'\"'\"'/g" \
    -e 's/[&\\/]/\\&/g; s/$/\\/; $s/\\$//'<<< "$new_comment"
)

For bash:

  1. Replace ' with '"'"'.

For sed:

  1. Escape &, \, / and line-terminators.

or what would be the faults?


Ideally this would not be done at all but curious to know.

Side comment:

Another solution, to keep it in one file, could be to add an exit in the script and add the text after that, then use sed or the like to print it. But that is beside my question.

#!/bin/bash

code code code

When in need of the comment:

sed -n '/^exit # EOF Script$/$ {/start_xyz/,/end_xyz/ ...}'

or what ever. Could even record offset and byte-length safely

code code

exit # EOF Script

start_xyz Blah blah blah blaah end_xyz

And thinking of it I guess something in the realm of:

comment=<<<'SOF'
...
SOF

Where one only need to replace any SOF to not end prematurely. Still my question is thesanitizing above. Thanks.

Err488
  • 25

1 Answers1

1

It seems you need to combine:

So:

#! /bin/bash -

comment=''

printf '%s\n' "$comment"

upgrade_script() { local - new_comment quoted_for_sh quoted_for_sed_and_sh set -o pipefail

Get comment:

new_comment="$(curl https://path/to/comment)" || return

quoted_for_sh='${new_comment//'/'\''}'

quoted_for_sed_and_sh=$( printf '%s\n' "$quoted_for_sh" | LC_ALL=C sed 's:[\/&]:\&:g;$!s/$/\/' ) || return

curl https://path/to/file |

Update comment='' with new_comment:

LC_ALL=C sed &quot;3s/comment=''/comment=$quoted_for_sed/&quot; &gt; updated_script

}

Though zsh+perl would be a better choice than bash+sed:

#! /bin/zsh -

comment=''

print -r -- $comment

upgrade_script() { set -o localoptions -o pipefail local new_comment quoted_for_sh

Get comment:

new_comment=$(curl https://path/to/comment) || return

quoted_for_sh=${(qq)new_comment}

curl https://path/to/file |

Update comment='' with new_comment:

perl -pse &quot;s/comment=\K''/$repl/ if $. == 3
  ' -- -repl=$quoted_for_sh &gt; updated_script

}

Bearing in mind that comment could end up containing a NUL character which is fine for zsh and its builtins or functions but can't be passed as argument to an external command. bash's $(...) removes the NULs.

To remove that risk, you can remove the NULs by hand with by piping the output of curl to tr -d '\0' or with

new_comment=${new_comment//$'\0'}

Or by bailing out if there's such a character:

[[ $new_comment = *$'\0'* ]] && die 'Something dodgy going on'
  • Ah, nice. That looks robust; and yes ~ perl would be a good alternative. It's not a dependency as is, but guess one could port the entire thing there. As is it is bash based though and have to see how much effort it is worth putting into it. – Err488 Jun 27 '23 at 01:34