CVE-2014-0593 (Other, published 2018-06-08T17:29Z)
Description: value: The set_version script as shipped with obs-service-set_version is a source validator for the Open Build Service (OBS). In versions prior to 0.5.3-1.1 this script did not properly sanitize the input provided by the user, allowing for code execution on the executing server.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/openSUSE/obs-service-set_version
Files changed:
set_version
set_version.old.do_not_use
Commit message: - replace bash script with a more secure perl version


Vulnerable code:
#!/bin/bash
# A simple script to update spec or dsc file
# very, very simple. I am happy about patches which handles multiple files with different version numbers
# defaults
MYVERSION=""
FILES=""

while test $# -gt 0; do
  case $1 in
    *-version)
      MYVERSION="$2"
      shift
    ;;
    *-file)
      FILES="$FILES ${2##*/}"
      shift
    ;;
    *-basename)
      BASENAME="$2"
      shift
    ;;
    *-outdir)
      MYOUTDIR="$2"
      shift
    ;;
    *)
      echo Unknown parameter $1.
      echo 'Usage: set_version --version $VERSION --file $FILE --basename $BASENAME --outdir $OUT'
      exit 1
    ;;
  esac
  shift
done

get_version_from_file () {
  # Search for tarball filenames including a version
  for ending in "tar.*" "tgz$" "tbz2$" "zip$" ; do
    if [ -z "$MYVERSION" ]; then
      MYVERSION=`ls -1t | sed -n "s,^$BASENAME.*[-_]\([0123456789].*\).${ending},\1,p" | head -n 1`
    else
      break;
    fi
  done
  # Search for a version in the root-directory name of the content's file list:
  for gz_ending in "tar.gz" "tgz" "tar.bz2" "tbz2" ; do
    if [ -z "$MYVERSION" ]; then
      MYVERSION=`tar tf $BASENAME*.${gz_ending} | sed -e 's,/.*,,' | uniq | sed -n "s,$BASENAME.*[-_]\(.*\),\1,p"`
    else
      break;
    fi
  done
  # Take version number (and optional revision) from Debian changelog
  if [ -z "$MYVERSION" ]; then
    MYVERSION=`head -n 1 *debian.changelog 2>/dev/null | sed -ne 's/.*(\(.*\)).*/\1/p'`
  fi
  if [ -z "$MYVERSION" ]; then
    echo "ERROR: no version is given and can't get detected automatically"
    exit 1
  fi

  # remove trailing ".orig", used by dpkg sources
  MYVERSION=`sed s/.orig$// <<<"$MYVERSION"`
write_files () {
  if [ -z "$FILES" ]; then
    FILES="*.spec *.dsc"
  fi
  if [ -z "$MYOUTDIR" ]; then
    echo "ERROR: no output directory is given via --outdir parameter!"
    exit 1
  fi

  for i in $FILES; do
    FILE=`ls -1 $i 2>/dev/null`
    [ -e "$FILE" ] || continue

    sed "0,/^Version:\(\s*\)[^%]*/s//Version:\1$MYVERSION/" "$FILE" > "$MYOUTDIR/$FILE" || exit 1
    if [ "${FILE%.spec}" != "$FILE" ]; then
      # set release back to zero after version upgrade, will be increased by OBS during build
      # also keep macros in release in case of fedora/mandriva
      sed -r -i "s,^Release:(\s*)[^%]*,Release:\10," "$MYOUTDIR/$FILE" || exit 1
   fi

    if [ "${FILE#_service:}" != "$FILE" ]; then
      # we can remove service files, no need to store them twice
      rm -f "$FILE"
    fi
  done

  if [ -e PKGBUILD ]; then
    FILE=`ls -1 _service:*[-_]"${MYVERSION}"* | head -n 1`
    [ -z "$FILE" ] && FILE=`ls -1 *[-_]"${MYVERSION}"* | head -n 1`
    MD5=`md5sum "$FILE" | head -n 1 | cut -d\  -f 1`

    sed "0,/^pkgver=\(\s*\)[^%]*/s//pkgver=\1$MYVERSION/" PKGBUILD > "$MYOUTDIR/PKGBUILD" || exit 1
    sed -r -i "s,^pkgrel=.*,pkgrel=0," "$MYOUTDIR/PKGBUILD" || exit 1
    sed -r -i "s,^md5sums=.*,md5sums=('$MD5')," "$MYOUTDIR/PKGBUILD" || exit 1
  fi
get_version_from_file
write_files
exit 0

Fixed code:
#!/usr/bin/perl

# A simple script to update version number in spec, dsc or arch linux files

my $version;
my $basename="";
my $outdir;
my @files;

use strict;
use Data::Dumper;
use File::Basename;

sub usage()
{
  print<<END

  Open Build Service source service "set_version"

  Used to update build description files with a detected or given version number.

  Required:

  --output:   mediaset, build only for the given mediaset, identify by name

  Options:

END
;
  exit;
}

while (@ARGV) {
  usage() if $ARGV[0] eq '--help';
  if ($ARGV[0] eq '--outdir') {
    shift @ARGV;
    $outdir = shift @ARGV;
    next;
  } elsif ($ARGV[0] eq '--version') {
    shift @ARGV;
    $version = shift @ARGV;
    next;
  } elsif ($ARGV[0] eq '--basename') {
    # this is actually more a prefix
    shift @ARGV;
    $basename = shift @ARGV;
    next;
  } elsif ($ARGV[0] eq '--file') {
    shift @ARGV;
    push @files, shift @ARGV;
    next;
  } else {
    die("Unknown argument $ARGV[0]!");
  }
  last;
usage() unless $outdir;

# get local file list
local *D;
opendir(D, ".") || return ();
my @srcfiles = grep {$_ ne '.' && $_ ne '..'} readdir(D);
closedir D;

# Detect version based on file names
unless ($version) {
  my @binsufs = qw{tar tar.gz tgz tar.bz2 tbz2 tar.xz zip};
  my $binsufsre = join('|', map {"\Q$_\E"} @binsufs);
  for my $name (@srcfiles) {
    if ($name =~ /^$basename.*[-_]([\d].*).(?:$binsufsre)$/) {
       $version=$1;
       last;
    }
  }
# to replace tags in .spec and .dsc files
sub replace_tag {
  my ($filename, $tag, $string) = @_;
  local *R;
  local *W;
  # read, try first an modified version in outdir
  if (!open(R, '<', "$outdir/$filename")) {
    if (!open(R, '<', $filename)) {
      die("unable to read $filename: $!\n");
      return undef;
    }
  }
  if (!open(W, '>', "$outdir/.$filename")) {
    die("unable to write .$filename: $!\n");
    return undef;
  }
  my $line;
  while (defined($line = <R>)) {
    $line =~ s/\n$//;
    if ( $filename =~ /PKGBUILD$/ ) {
      $line =~ s/^$tag=.*/$tag=$string/;
    } else {
      # keep inline macros for rpm
      $line =~ s/^$tag:(\s*)[^%]*/$tag:$1$string/;
    }
    print W "$line\n";
  }
  close R;
  close W;
  rename("$outdir/.$filename", "$outdir/$filename") || die("rename failed");
}

# handle rpm spec and debian dsc files
@files = grep {$_ =~ /.(spec|dsc)$/} @srcfiles if @files <= 0;
for my $file (@files) {
  replace_tag($file, 'Version', $version);
  replace_tag($file, 'Release', "0");
}

use Digest::MD5;

# handle arch linux PKGBUILD files
if ( -e "PKGBUILD" ) {
  # find md5sum of tar ball
  my $md5sum;
  my $tarfile;
  for my $file (@srcfiles) {
    if ( $file =~ /^_service:.*[-_]$version.*/ ) {
      $tarfile = $file;
      last;
    }
  }
  unless($tarfile) {
    for my $file (@srcfiles) {
      if ( $file =~ /.*[-_]$version.*/ ) {
        $tarfile = $file;
        last;
      }
    }
  }

  open(FILE, $tarfile) || die "Unable to find source file to calculate md5sum\n";
  my $ctx = Digest::MD5->new;
  $ctx->addfile(*FILE);
  $md5sum = $ctx->hexdigest;
  close(FILE);

  die ("Failed to calculate md5sum") unless $md5sum;
  replace_tag("PKGBUILD", "pkgrel", "0");
  replace_tag("PKGBUILD", "md5sums", "('".$md5sum."')");
}
#!/bin/bash

# A simple script to update spec or dsc file
# very, very simple. I am happy about patches which handles multiple files with different version numbers
#
# (C) 2010 by Adrian Schröter <adrian@suse.de>
#  
# This program is free software; you can redistribute it and/or  
# modify it under the terms of the GNU General Public License  
# as published by the Free Software Foundation; either version 2  
# of the License, or (at your option) any later version.  
# See http://www.gnu.org/licenses/gpl-2.0.html for full license text.  


# defaults
MYVERSION=""
FILES=""

while test $# -gt 0; do
  case $1 in
    *-version)
      MYVERSION="$2"
      shift
    ;;
    *-file)
      FILES="$FILES ${2##*/}"
      shift
    ;;
    *-basename)
      BASENAME="$2"
      shift
    ;;
    *-outdir)
      MYOUTDIR="$2"
      shift
    ;;
    *)
      echo Unknown parameter $1.
      echo 'Usage: set_version --version $VERSION --file $FILE --basename $BASENAME --outdir $OUT'
      exit 1
    ;;
  esac
  shift
done

get_version_from_file () {
  # Search for tarball filenames including a version
  for ending in "tar.*" "tgz$" "tbz2$" "zip$" ; do
    if [ -z "$MYVERSION" ]; then
      MYVERSION=`ls -1t | sed -n "s,^$BASENAME.*[-_]\([0123456789].*\).${ending},\1,p" | head -n 1`
    else
      break;
    fi
  done
  # Search for a version in the root-directory name of the content's file list:
  for gz_ending in "tar.gz" "tgz" "tar.bz2" "tbz2" ; do
    if [ -z "$MYVERSION" ]; then
      MYVERSION=`tar tf $BASENAME*.${gz_ending} | sed -e 's,/.*,,' | uniq | sed -n "s,$BASENAME.*[-_]\(.*\),\1,p"`
    else
      break;
    fi
  done
  # Take version number (and optional revision) from Debian changelog
  if [ -z "$MYVERSION" ]; then
    MYVERSION=`head -n 1 *debian.changelog 2>/dev/null | sed -ne 's/.*(\(.*\)).*/\1/p'`
  fi
  if [ -z "$MYVERSION" ]; then
    echo "ERROR: no version is given and can't get detected automatically"
    exit 1
  fi

  # remove trailing ".orig", used by dpkg sources
  MYVERSION=`sed s/.orig$// <<<"$MYVERSION"`
}

write_files () {
  if [ -z "$FILES" ]; then
    FILES="*.spec *.dsc"
  fi
  if [ -z "$MYOUTDIR" ]; then
    echo "ERROR: no output directory is given via --outdir parameter!"
    exit 1
  fi

  for i in $FILES; do
    FILE=`ls -1 $i 2>/dev/null`
    [ -e "$FILE" ] || continue

    sed "0,/^Version:\(\s*\)[^%]*/s//Version:\1$MYVERSION/" "$FILE" > "$MYOUTDIR/$FILE" || exit 1
    if [ "${FILE%.spec}" != "$FILE" ]; then
      # set release back to zero after version upgrade, will be increased by OBS during build
      # also keep macros in release in case of fedora/mandriva
      sed -r -i "s,^Release:(\s*)[^%]*,Release:\10," "$MYOUTDIR/$FILE" || exit 1
   fi

    if [ "${FILE#_service:}" != "$FILE" ]; then
      # we can remove service files, no need to store them twice
      rm -f "$FILE"
    fi
  done

  if [ -e PKGBUILD ]; then
    FILE=`ls -1 _service:*[-_]"${MYVERSION}"* | head -n 1`
    [ -z "$FILE" ] && FILE=`ls -1 *[-_]"${MYVERSION}"* | head -n 1`
    MD5=`md5sum "$FILE" | head -n 1 | cut -d\  -f 1`

    sed "0,/^pkgver=\(\s*\)[^%]*/s//pkgver=\1$MYVERSION/" PKGBUILD > "$MYOUTDIR/PKGBUILD" || exit 1
    sed -r -i "s,^pkgrel=.*,pkgrel=0," "$MYOUTDIR/PKGBUILD" || exit 1
    sed -r -i "s,^md5sums=.*,md5sums=('$MD5')," "$MYOUTDIR/PKGBUILD" || exit 1
  fi
}

get_version_from_file
write_files

exit 0