Fix copylocks error about copying IPTables.

IPTables.connections contains a sync.RWMutex. Copying it will trigger copylocks
analysis. Tested by manually enabling nogo tests.

sync.RWMutex is added to IPTables for the additional race condition discovered.

PiperOrigin-RevId: 314817019
This commit is contained in:
Ting-Yu Wang
2020-06-05 11:29:09 -07:00
committed by Nicolas Lacasse
parent f766366091
commit 41da7a568b
11 changed files with 71 additions and 81 deletions
+15 -21
View File
@@ -144,31 +144,27 @@ func GetEntries(t *kernel.Task, stack *stack.Stack, outPtr usermem.Addr, outLen
}
func findTable(stk *stack.Stack, tablename linux.TableName) (stack.Table, error) {
ipt := stk.IPTables()
table, ok := ipt.Tables[tablename.String()]
table, ok := stk.IPTables().GetTable(tablename.String())
if !ok {
return stack.Table{}, fmt.Errorf("couldn't find table %q", tablename)
}
return table, nil
}
// FillDefaultIPTables sets stack's IPTables to the default tables and
// populates them with metadata.
func FillDefaultIPTables(stk *stack.Stack) {
ipt := stack.DefaultTables()
// In order to fill in the metadata, we have to translate ipt from its
// netstack format to Linux's giant-binary-blob format.
for name, table := range ipt.Tables {
_, metadata, err := convertNetstackToBinary(name, table)
if err != nil {
panic(fmt.Errorf("Unable to set default IP tables: %v", err))
// FillIPTablesMetadata populates stack's IPTables with metadata.
func FillIPTablesMetadata(stk *stack.Stack) {
stk.IPTables().ModifyTables(func(tables map[string]stack.Table) {
// In order to fill in the metadata, we have to translate ipt from its
// netstack format to Linux's giant-binary-blob format.
for name, table := range tables {
_, metadata, err := convertNetstackToBinary(name, table)
if err != nil {
panic(fmt.Errorf("Unable to set default IP tables: %v", err))
}
table.SetMetadata(metadata)
tables[name] = table
}
table.SetMetadata(metadata)
ipt.Tables[name] = table
}
stk.SetIPTables(ipt)
})
}
// convertNetstackToBinary converts the iptables as stored in netstack to the
@@ -573,15 +569,13 @@ func SetEntries(stk *stack.Stack, optVal []byte) *syserr.Error {
// - There are no chains without an unconditional final rule.
// - There are no chains without an unconditional underflow rule.
ipt := stk.IPTables()
table.SetMetadata(metadata{
HookEntry: replace.HookEntry,
Underflow: replace.Underflow,
NumEntries: replace.NumEntries,
Size: replace.Size,
})
ipt.Tables[replace.Name.String()] = table
stk.SetIPTables(ipt)
stk.IPTables().ReplaceTable(replace.Name.String(), table)
return nil
}
+4 -5
View File
@@ -362,14 +362,13 @@ func (s *Stack) RouteTable() []inet.Route {
}
// IPTables returns the stack's iptables.
func (s *Stack) IPTables() (stack.IPTables, error) {
func (s *Stack) IPTables() (*stack.IPTables, error) {
return s.Stack.IPTables(), nil
}
// FillDefaultIPTables sets the stack's iptables to the default tables, which
// allow and do not modify all traffic.
func (s *Stack) FillDefaultIPTables() {
netfilter.FillDefaultIPTables(s.Stack)
// FillIPTablesMetadata populates stack's IPTables with metadata.
func (s *Stack) FillIPTablesMetadata() {
netfilter.FillIPTablesMetadata(s.Stack)
}
// Resume implements inet.Stack.Resume.
+36 -6
View File
@@ -43,11 +43,11 @@ const HookUnset = -1
// DefaultTables returns a default set of tables. Each chain is set to accept
// all packets.
func DefaultTables() IPTables {
func DefaultTables() *IPTables {
// TODO(gvisor.dev/issue/170): We may be able to swap out some strings for
// iotas.
return IPTables{
Tables: map[string]Table{
return &IPTables{
tables: map[string]Table{
TablenameNat: Table{
Rules: []Rule{
Rule{Target: AcceptTarget{}},
@@ -106,7 +106,7 @@ func DefaultTables() IPTables {
UserChains: map[string]int{},
},
},
Priorities: map[Hook][]string{
priorities: map[Hook][]string{
Input: []string{TablenameNat, TablenameFilter},
Prerouting: []string{TablenameMangle, TablenameNat},
Output: []string{TablenameMangle, TablenameNat, TablenameFilter},
@@ -158,6 +158,36 @@ func EmptyNatTable() Table {
}
}
// GetTable returns table by name.
func (it *IPTables) GetTable(name string) (Table, bool) {
it.mu.RLock()
defer it.mu.RUnlock()
t, ok := it.tables[name]
return t, ok
}
// ReplaceTable replaces or inserts table by name.
func (it *IPTables) ReplaceTable(name string, table Table) {
it.mu.Lock()
defer it.mu.Unlock()
it.tables[name] = table
}
// ModifyTables acquires write-lock and calls fn with internal name-to-table
// map. This function can be used to update multiple tables atomically.
func (it *IPTables) ModifyTables(fn func(map[string]Table)) {
it.mu.Lock()
defer it.mu.Unlock()
fn(it.tables)
}
// GetPriorities returns slice of priorities associated with hook.
func (it *IPTables) GetPriorities(hook Hook) []string {
it.mu.RLock()
defer it.mu.RUnlock()
return it.priorities[hook]
}
// A chainVerdict is what a table decides should be done with a packet.
type chainVerdict int
@@ -184,8 +214,8 @@ func (it *IPTables) Check(hook Hook, pkt *PacketBuffer, gso *GSO, r *Route, addr
it.connections.HandlePacket(pkt, hook, gso, r)
// Go through each table containing the hook.
for _, tablename := range it.Priorities[hook] {
table := it.Tables[tablename]
for _, tablename := range it.GetPriorities(hook) {
table, _ := it.GetTable(tablename)
ruleIdx := table.BuiltinChains[hook]
switch verdict := it.checkChain(hook, pkt, table, ruleIdx, gso, r, address, nicName); verdict {
// If the table returns Accept, move on to the next table.
+10 -5
View File
@@ -16,6 +16,7 @@ package stack
import (
"strings"
"sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
@@ -78,13 +79,17 @@ const (
// IPTables holds all the tables for a netstack.
type IPTables struct {
// Tables maps table names to tables. User tables have arbitrary names.
Tables map[string]Table
// mu protects tables and priorities.
mu sync.RWMutex
// Priorities maps each hook to a list of table names. The order of the
// tables maps table names to tables. User tables have arbitrary names. mu
// needs to be locked for accessing.
tables map[string]Table
// priorities maps each hook to a list of table names. The order of the
// list is the order in which each table should be visited for that
// hook.
Priorities map[Hook][]string
// hook. mu needs to be locked for accessing.
priorities map[Hook][]string
connections ConnTrackTable
}
+5 -18
View File
@@ -424,12 +424,8 @@ type Stack struct {
// handleLocal allows non-loopback interfaces to loop packets.
handleLocal bool
// tablesMu protects iptables.
tablesMu sync.RWMutex
// tables are the iptables packet filtering and manipulation rules. The are
// protected by tablesMu.`
tables IPTables
// tables are the iptables packet filtering and manipulation rules.
tables *IPTables
// resumableEndpoints is a list of endpoints that need to be resumed if the
// stack is being restored.
@@ -676,6 +672,7 @@ func New(opts Options) *Stack {
clock: clock,
stats: opts.Stats.FillIn(),
handleLocal: opts.HandleLocal,
tables: DefaultTables(),
icmpRateLimiter: NewICMPRateLimiter(),
seed: generateRandUint32(),
ndpConfigs: opts.NDPConfigs,
@@ -1741,18 +1738,8 @@ func (s *Stack) IsInGroup(nicID tcpip.NICID, multicastAddr tcpip.Address) (bool,
}
// IPTables returns the stack's iptables.
func (s *Stack) IPTables() IPTables {
s.tablesMu.RLock()
t := s.tables
s.tablesMu.RUnlock()
return t
}
// SetIPTables sets the stack's iptables.
func (s *Stack) SetIPTables(ipt IPTables) {
s.tablesMu.Lock()
s.tables = ipt
s.tablesMu.Unlock()
func (s *Stack) IPTables() *IPTables {
return s.tables
}
// ICMPLimit returns the maximum number of ICMP messages that can be sent
-5
View File
@@ -140,11 +140,6 @@ func (e *endpoint) SetOwner(owner tcpip.PacketOwner) {
e.owner = owner
}
// IPTables implements tcpip.Endpoint.IPTables.
func (e *endpoint) IPTables() (stack.IPTables, error) {
return e.stack.IPTables(), nil
}
// Read reads data from the endpoint. This method does not block if
// there is no data pending.
func (e *endpoint) Read(addr *tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
-5
View File
@@ -132,11 +132,6 @@ func (ep *endpoint) Close() {
// ModerateRecvBuf implements tcpip.Endpoint.ModerateRecvBuf.
func (ep *endpoint) ModerateRecvBuf(copied int) {}
// IPTables implements tcpip.Endpoint.IPTables.
func (ep *endpoint) IPTables() (stack.IPTables, error) {
return ep.stack.IPTables(), nil
}
// Read implements tcpip.Endpoint.Read.
func (ep *endpoint) Read(addr *tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
ep.rcvMu.Lock()
-5
View File
@@ -166,11 +166,6 @@ func (e *endpoint) SetOwner(owner tcpip.PacketOwner) {
e.owner = owner
}
// IPTables implements tcpip.Endpoint.IPTables.
func (e *endpoint) IPTables() (stack.IPTables, error) {
return e.stack.IPTables(), nil
}
// Read implements tcpip.Endpoint.Read.
func (e *endpoint) Read(addr *tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
if !e.associated {
-5
View File
@@ -1172,11 +1172,6 @@ func (e *endpoint) SetOwner(owner tcpip.PacketOwner) {
e.owner = owner
}
// IPTables implements tcpip.Endpoint.IPTables.
func (e *endpoint) IPTables() (stack.IPTables, error) {
return e.stack.IPTables(), nil
}
// Read reads data from the endpoint.
func (e *endpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
e.LockUser()
-5
View File
@@ -247,11 +247,6 @@ func (e *endpoint) Close() {
// ModerateRecvBuf implements tcpip.Endpoint.ModerateRecvBuf.
func (e *endpoint) ModerateRecvBuf(copied int) {}
// IPTables implements tcpip.Endpoint.IPTables.
func (e *endpoint) IPTables() (stack.IPTables, error) {
return e.stack.IPTables(), nil
}
// Read reads data from the endpoint. This method does not block if
// there is no data pending.
func (e *endpoint) Read(addr *tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
+1 -1
View File
@@ -1056,7 +1056,7 @@ func newEmptySandboxNetworkStack(clock tcpip.Clock, uniqueID stack.UniqueID) (in
return nil, fmt.Errorf("SetTransportProtocolOption failed: %v", err)
}
s.FillDefaultIPTables()
s.FillIPTablesMetadata()
return &s, nil
}