Class: Pedant::CheckSocketLeak

Inherits:
Check
  • Object
show all
Defined in:
lib/pedant/checks/socket_leak.rb

Instance Attribute Summary

Attributes inherited from Check

#result

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Check

all, depends, #fail, #fatal, friendly_name, inherited, #initialize, initialize!, list, #pass, provides, ready?, #report, run_checks_in_dependency_order, #skip, #warn

Constructor Details

This class inherits a constructor from Pedant::Check

Class Method Details

.requiresObject



29
30
31
# File 'lib/pedant/checks/socket_leak.rb', line 29

def self.requires
  super + [:trees]
end

Instance Method Details

#block_parser(block, found) ⇒ Object

Iterates over the blocks and hands individual nodes up to the node_parser

Parameters:

  • block

    the current Block node to examine

  • found

    the current list of found open_sock_tcp

  • all

    the found open_sock_tcp that haven’t been closed



161
162
163
164
165
166
# 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) ⇒ Object

Breaks the tree up into functions and feeds them into block_parser

Parameters:

  • file

    the current file being examined

  • tree

    the entire file tree



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# 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) ⇒ Object

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

Parameters:

  • bnode

    the node to examine

  • found

    a set of active “open_sock_tcp” items

Returns:

  • the new list of open_sock_tcp items



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 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) ⇒ Object

If the allFound contains anything then throw up a warning



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 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

#runObject



181
182
183
184
185
186
187
# 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