class Pedant::CheckSocketLeak

Public Class Methods

requires() click to toggle source
Calls superclass method Pedant::Check::requires
# File lib/pedant/checks/socket_leak.rb, line 29
def self.requires
  super + [:trees]
end

Public Instance Methods

block_parser(block, found) click to toggle source

Iterates over the blocks and hands individual nodes up to the node_parser @param block the current Block node to examine @param found the current list of found open_sock_tcp @param all the found open_sock_tcp that haven't been closed

# File lib/pedant/checks/socket_leak.rb, line 161
def block_parser(block, found)
  block.each do |node|
    found = node_parser(node, found);
  end
  return found;
end
check(file, tree) click to toggle source

Breaks the tree up into functions and feeds them into block_parser @param file the current file being examined @param tree the entire file tree

# File lib/pedant/checks/socket_leak.rb, line 38
def check(file, tree)

  ##
  # If the allFound contains anything then throw up a warning
  ##
  def report_findings(allFound)
    if allFound.size() > 0
      warn
      output = ""
      allFound.each do |handle|
        if !output.empty?
          output += ", "
        end
        if handle == ""
          handle = "<unassigned>"
        end
        output += handle
      end
      report(:warn, "Possibly leaked socket handle(s): " + output)
    end
  end
 
  ##
  # Examines a single passed in node and tries to appropriately handle it
  # based on the type. This function ignores "g_sock" and _ssh_socket as both
  # of these are handled in a way that this parser can't really handle
  #
  # @param bnode the node to examine
  # @param found a set of active "open_sock_tcp" items
  # @return the new list of open_sock_tcp items
  ##
  def node_parser(bnode, found)
      if bnode.is_a?(Nasl::Assignment)
        name = ""
        if bnode.lval.is_a?(Nasl::Lvalue)
          name = bnode.lval.ident.name;
        end
        if bnode.expr.is_a?(Nasl::Call)
          if ((bnode.expr.name.ident.name == "open_sock_tcp" ||
              bnode.expr.name.ident.name == "http_open_socket") &&
              name != "g_sock" && name != "_ssh_socket")
            found.add(name)
          end
        end
      elsif bnode.is_a?(Nasl::Local)
        bnode.idents.each do |idents|
          if idents.is_a?(Nasl::Assignment)
            name = ""
            if idents.lval.is_a?(Nasl::Lvalue)
              name = idents.lval.ident.name
            elsif idents.lval.is_a?(Nasl::Identifier)
              name = idents.lval.name
            end
            if idents.expr.is_a?(Nasl::Call)
              if ((idents.expr.name.ident.name == "open_sock_tcp" ||
                  idents.expr.name.ident.name == "http_open_socket")  &&
                  name != "g_sock" && name != "_ssh_socket")
                found.add(name)
              end
            end
          end
        end
      elsif bnode.is_a?(Nasl::Return)
        # if the socket we are tracking gets returned then never mark it as
        # a leak
        if bnode.expr.is_a?(Nasl::Lvalue)
            found = found - [bnode.expr.ident.name]
        end
      elsif (bnode.is_a?(Nasl::Break) || bnode.is_a?(Nasl::Continue) ||
          (bnode.is_a?(Nasl::Call) && (bnode.name.ident.name == "exit" ||
                                      bnode.name.ident.name == "audit")))
        report_findings(found)
      elsif bnode.is_a?(Nasl::If)
        if (bnode.cond.is_a?(Nasl::Expression) and bnode.cond.rhs.is_a?(Nasl::Lvalue))
          if bnode.cond.op.to_s() == "!"
            if found.any? {|varName| varName == bnode.cond.rhs.ident.name}
              # don't go down this path. This is the !soc path
              return found;
            end
          end
        end
        # the if statement provides us with a block we can peak down to.
        # However, it isn't always enumerable so handle accordingly
        if (bnode.true.is_a?(Enumerable))
          found = block_parser(bnode.true, found)
        else
          found = node_parser(bnode.true, found);
        end
        if (bnode.false.is_a?(Enumerable))
          found = block_parser(bnode.false, found)
        else
          found = node_parser(bnode.false, found);
        end
      elsif bnode.is_a?(Nasl::Block)
        found = block_parser(bnode.body, found);
      elsif bnode.is_a?(Nasl::Call)
        if (bnode.name.ident.name == "open_sock_tcp" ||
            bnode.name.ident.name == "http_open_socket")
          found.add("")
        elsif (bnode.name.ident.name == "close" ||
               bnode.name.ident.name == "ftp_close" ||
               bnode.name.ident.name == "http_close_socket" ||
               bnode.name.ident.name == "smtp_close")
          # Check that this is an Lvalue. It could be a call or something
          # which is just too complicated to handle and doesn't really work
          # with our variable tracking system
          if bnode.args[0].expr.is_a?(Nasl::Lvalue)
            found = found - [bnode.args[0].expr.ident.name]
          end
        elsif (bnode.name.ident.name == "session_init" ||
               bnode.name.ident.name == "ssh_close_connection")
          pass
        end
      end
      return found
    end

  ##
  # Iterates over the blocks and hands individual nodes up to the node_parser
  # @param block the current Block node to examine
  # @param found the current list of found open_sock_tcp
  # @param all the found open_sock_tcp that haven't been closed
  ##
  def block_parser(block, found)
    block.each do |node|
      found = node_parser(node, found);
    end
    return found;
  end
  
  # Extract by the block. Will help us since we don't dive down into all
  # blocks as of yet (only if statements)
  allFound = Set.new
  tree.all(:Function).each do |node|
    allFound.merge(block_parser(node.body, Set.new))
  end
 
  # The main body of a file is not a Block, so it must be considered
  # separately.
  allFound.merge(block_parser(tree, Set.new))
  report_findings(allFound)
end
node_parser(bnode, found) click to toggle source

Examines a single passed in node and tries to appropriately handle it based on the type. This function ignores “g_sock” and _ssh_socket as both of these are handled in a way that this parser can't really handle

@param bnode the node to examine @param found a set of active “open_sock_tcp” items @return the new list of open_sock_tcp items

# File lib/pedant/checks/socket_leak.rb, line 69
def node_parser(bnode, found)
    if bnode.is_a?(Nasl::Assignment)
      name = ""
      if bnode.lval.is_a?(Nasl::Lvalue)
        name = bnode.lval.ident.name;
      end
      if bnode.expr.is_a?(Nasl::Call)
        if ((bnode.expr.name.ident.name == "open_sock_tcp" ||
            bnode.expr.name.ident.name == "http_open_socket") &&
            name != "g_sock" && name != "_ssh_socket")
          found.add(name)
        end
      end
    elsif bnode.is_a?(Nasl::Local)
      bnode.idents.each do |idents|
        if idents.is_a?(Nasl::Assignment)
          name = ""
          if idents.lval.is_a?(Nasl::Lvalue)
            name = idents.lval.ident.name
          elsif idents.lval.is_a?(Nasl::Identifier)
            name = idents.lval.name
          end
          if idents.expr.is_a?(Nasl::Call)
            if ((idents.expr.name.ident.name == "open_sock_tcp" ||
                idents.expr.name.ident.name == "http_open_socket")  &&
                name != "g_sock" && name != "_ssh_socket")
              found.add(name)
            end
          end
        end
      end
    elsif bnode.is_a?(Nasl::Return)
      # if the socket we are tracking gets returned then never mark it as
      # a leak
      if bnode.expr.is_a?(Nasl::Lvalue)
          found = found - [bnode.expr.ident.name]
      end
    elsif (bnode.is_a?(Nasl::Break) || bnode.is_a?(Nasl::Continue) ||
        (bnode.is_a?(Nasl::Call) && (bnode.name.ident.name == "exit" ||
                                    bnode.name.ident.name == "audit")))
      report_findings(found)
    elsif bnode.is_a?(Nasl::If)
      if (bnode.cond.is_a?(Nasl::Expression) and bnode.cond.rhs.is_a?(Nasl::Lvalue))
        if bnode.cond.op.to_s() == "!"
          if found.any? {|varName| varName == bnode.cond.rhs.ident.name}
            # don't go down this path. This is the !soc path
            return found;
          end
        end
      end
      # the if statement provides us with a block we can peak down to.
      # However, it isn't always enumerable so handle accordingly
      if (bnode.true.is_a?(Enumerable))
        found = block_parser(bnode.true, found)
      else
        found = node_parser(bnode.true, found);
      end
      if (bnode.false.is_a?(Enumerable))
        found = block_parser(bnode.false, found)
      else
        found = node_parser(bnode.false, found);
      end
    elsif bnode.is_a?(Nasl::Block)
      found = block_parser(bnode.body, found);
    elsif bnode.is_a?(Nasl::Call)
      if (bnode.name.ident.name == "open_sock_tcp" ||
          bnode.name.ident.name == "http_open_socket")
        found.add("")
      elsif (bnode.name.ident.name == "close" ||
             bnode.name.ident.name == "ftp_close" ||
             bnode.name.ident.name == "http_close_socket" ||
             bnode.name.ident.name == "smtp_close")
        # Check that this is an Lvalue. It could be a call or something
        # which is just too complicated to handle and doesn't really work
        # with our variable tracking system
        if bnode.args[0].expr.is_a?(Nasl::Lvalue)
          found = found - [bnode.args[0].expr.ident.name]
        end
      elsif (bnode.name.ident.name == "session_init" ||
             bnode.name.ident.name == "ssh_close_connection")
        pass
      end
    end
    return found
  end
report_findings(allFound) click to toggle source

If the allFound contains anything then throw up a warning

# File lib/pedant/checks/socket_leak.rb, line 43
def report_findings(allFound)
  if allFound.size() > 0
    warn
    output = ""
    allFound.each do |handle|
      if !output.empty?
        output += ", "
      end
      if handle == ""
        handle = "<unassigned>"
      end
      output += handle
    end
    report(:warn, "Possibly leaked socket handle(s): " + output)
  end
end
run() click to toggle source
# File lib/pedant/checks/socket_leak.rb, line 181
def run
  # This check will pass by default.
  pass

  # Run this check on the tree from every file.
  @kb[:trees].each { |file, tree| check(file, tree) }
end