Skip to content

Commit 42babcd

Browse files
ldxldx
authored andcommitted
Preserve per-protocol family libxtables globals.
Libxtables uses quite a few global variables, and unfortunately there are protocol-specific extensions using the same name for multiple protocols, e.g. REJECT exists both for IPv4 and for IPv6. Since registering/looking up an extension is based on the name of the extension, this means the protocol whose extension is registered first will prevent any further extensions with the same name from registering. Solution: save our per-protocol libxtables global variables every time after we returned from a call into libxtables, and restore them before calling again. We're using singleton xtables instances for each protocol family, so we can manage that on a per-instance basis. However, extensions with AF_UNSPEC are only registered once, for the first protocol that uses them. Call their init function manually to force them to register to our protocol family as well, but record each loaded extension. This is necessary, since xtables will just exit() if it finds that an extension has already been registered.
1 parent 44b5e94 commit 42babcd

File tree

2 files changed

+157
-3
lines changed

2 files changed

+157
-3
lines changed

iptc/xtables.py

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,16 @@ class xtables_target(ct.Union):
670670
("v10", _xtables_target_v10)]
671671

672672

673+
class xtables_afinfo(ct.Structure):
674+
_fields_ = [("kmod", ct.c_char_p),
675+
("proc_exists", ct.c_char_p),
676+
("libprefix", ct.c_char_p),
677+
("family", ct.c_uint8),
678+
("ipproto", ct.c_uint8),
679+
("so_rev_match", ct.c_int),
680+
("so_rev_target", ct.c_int)]
681+
682+
673683
class XTablesError(Exception):
674684
"""Raised when an xtables call fails for some reason."""
675685

@@ -698,6 +708,10 @@ class XTablesError(Exception):
698708
_wrap_uintfn.restype = ct.c_int
699709
_wrap_uintfn.argtypes = [ct.c_void_p, ct.c_uint]
700710

711+
_wrap_voidfn = _lib_xtwrapper.wrap_voidfn
712+
_wrap_voidfn.restype = ct.c_int
713+
_wrap_voidfn.argtypes = [ct.c_void_p]
714+
701715
_wrap_x6fn = _lib_xtwrapper.wrap_x6fn
702716
_wrap_x6fn.restype = ct.c_int
703717
_wrap_x6fn.argtypes = [ct.c_void_p, ct.c_void_p]
@@ -713,6 +727,20 @@ def _xt_exit(status, *args):
713727
_xt_exit = _EXIT_FN(_xt_exit)
714728

715729

730+
def preserve_globals(fn):
731+
def new(*args):
732+
obj = args[0]
733+
obj._restore_globals()
734+
try:
735+
ret = fn(*args)
736+
except Exception:
737+
obj._save_globals()
738+
raise
739+
obj._save_globals()
740+
return ret
741+
return new
742+
743+
716744
class xtables(object):
717745
_xtables_init_all = _lib_xtables.xtables_init_all
718746
_xtables_init_all.restype = ct.c_int
@@ -726,16 +754,26 @@ class xtables(object):
726754
_xtables_find_target.restype = ct.POINTER(xtables_target)
727755
_xtables_find_target.argtypes = [ct.c_char_p, ct.c_int]
728756

757+
_xtables_afinfo = ct.c_void_p.in_dll(_lib_xtables, "afinfo")
758+
_xtables_xt_params = ct.c_void_p.in_dll(_lib_xtables, "xt_params")
759+
_xtables_matches = (ct.c_void_p.in_dll(_lib_xtables, "xtables_matches"))
760+
_xtables_pending_matches = (ct.c_void_p.in_dll(_lib_xtables,
761+
"xtables_pending_matches"))
762+
_xtables_targets = (ct.c_void_p.in_dll(_lib_xtables, "xtables_targets"))
763+
_xtables_pending_targets = (ct.c_void_p.in_dll(_lib_xtables,
764+
"xtables_pending_targets"))
765+
729766
_cache = weakref.WeakValueDictionary()
730767

731768
def __new__(cls, proto):
732769
obj = xtables._cache.get(proto, None)
733770
if not obj:
734771
obj = object.__new__(cls)
735772
xtables._cache[proto] = obj
773+
obj._xtinit(proto)
736774
return obj
737775

738-
def __init__(self, proto):
776+
def _xtinit(self, proto):
739777
self.proto = proto
740778
self._xt_globals = xtables_globals()
741779
self._xt_globals.option_offset = 0
@@ -744,13 +782,52 @@ def __init__(self, proto):
744782
self._xt_globals.orig_opts = None
745783
self._xt_globals.opts = None
746784
self._xt_globals.exit_err = _xt_exit
785+
786+
self._loaded_exts = []
787+
788+
# make sure we're initializing with clean state
789+
self._afinfo = ct.c_void_p(None).value
790+
self._xt_params = ct.c_void_p(None).value
791+
self._matches = ct.c_void_p(None).value
792+
self._pending_matches = ct.c_void_p(None).value
793+
self._targets = ct.c_void_p(None).value
794+
self._pending_targets = ct.c_void_p(None).value
795+
747796
rv = xtables._xtables_init_all(ct.pointer(self._xt_globals), proto)
748797
if rv:
749798
raise XTablesError("xtables_init_all() failed: %d" % (rv))
799+
self._save_globals()
750800

751801
def __repr__(self):
752802
return "XTables for protocol %d" % (self.proto)
753803

804+
def _save_globals(self):
805+
# Save our per-protocol libxtables global variables, and set them to
806+
# NULL so that we don't interfere with other protocols.
807+
null = ct.c_void_p(None)
808+
self._afinfo = xtables._xtables_afinfo.value
809+
xtables._xtables_afinfo.value = null.value
810+
self._xt_params = xtables._xtables_xt_params.value
811+
xtables._xtables_xt_params.value = null.value
812+
self._matches = xtables._xtables_matches.value
813+
xtables._xtables_matches.value = null.value
814+
self._pending_matches = xtables._xtables_pending_matches.value
815+
xtables._xtables_pending_matches.value = null.value
816+
self._targets = xtables._xtables_targets.value
817+
xtables._xtables_targets.value = null.value
818+
self._pending_targets = xtables._xtables_pending_targets.value
819+
xtables._xtables_pending_targets.value = null.value
820+
821+
def _restore_globals(self):
822+
# Restore per-protocol libxtables global variables saved in
823+
# _save_globals().
824+
xtables._xtables_afinfo.value = self._afinfo
825+
xtables._xtables_xt_params.value = self._xt_params
826+
xtables._xtables_matches.value = self._matches
827+
xtables._xtables_pending_matches.value = self._pending_matches
828+
xtables._xtables_targets.value = self._targets
829+
xtables._xtables_pending_targets.value = self._pending_targets
830+
754831
@classmethod
755832
def _get_xtables_version(cls, version):
756833
version_match = re.match("libxtables.so.(\d+)", version)
@@ -760,10 +837,62 @@ def _get_xtables_version(cls, version):
760837
else:
761838
raise RuntimeError("Xtables returned unknown version format")
762839

840+
def _check_extname(self, name):
841+
if name in ["", "ACCEPT", "DROP", "QUEUE", "RETURN"]:
842+
name = "standard"
843+
return name
844+
845+
def _loaded(self, name):
846+
self._loaded_exts.append(name)
847+
848+
def _is_loaded(self, name):
849+
if name in self._loaded_exts:
850+
return True
851+
else:
852+
return False
853+
854+
def _get_initfn_from_lib(self, name, lib):
855+
try:
856+
initfn = getattr(lib, "libxt_%s_init" % (name))
857+
except AttributeError:
858+
afinfo = ct.cast(self._afinfo, ct.POINTER(xtables_afinfo))
859+
prefix = afinfo[0].libprefix
860+
initfn = getattr(lib, "%s%s_init" % (prefix, name), None)
861+
return initfn
862+
863+
def _try_extinit(self, name, lib):
864+
try:
865+
if type(lib) != ct.CDLL:
866+
lib = ct.CDLL(lib)
867+
fn = self._get_initfn_from_lib(name, lib)
868+
if fn:
869+
_wrap_voidfn(fn)
870+
return True
871+
except:
872+
pass
873+
return False
874+
875+
def _try_register(self, name):
876+
if self._try_extinit(name, _lib_xtables):
877+
return
878+
afinfo = ct.cast(self._afinfo, ct.POINTER(xtables_afinfo))
879+
prefix = afinfo[0].libprefix
880+
libs = ["/lib/xtables/libxt_" + name + ".so",
881+
"/lib/xtables/" + prefix + name + ".so"]
882+
for lib in libs:
883+
if self._try_extinit(name, lib):
884+
return
885+
886+
@preserve_globals
763887
def find_match(self, name):
888+
name = self._check_extname(name)
764889
match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None)
765890
if not match:
766-
return match
891+
self._try_register(name)
892+
match = xtables._xtables_find_match(name, XTF_TRY_LOAD, None)
893+
if not match:
894+
return match
895+
self._loaded(name)
767896
version = xtables._get_xtables_version(match.contents.v1.version)
768897

769898
if 1 == version:
@@ -785,10 +914,16 @@ def find_match(self, name):
785914
else:
786915
raise Exception("Match object casting failed")
787916

917+
@preserve_globals
788918
def find_target(self, name):
919+
name = self._check_extname(name)
789920
target = xtables._xtables_find_target(name, XTF_TRY_LOAD)
790921
if not target:
791-
return target
922+
self._try_register(name)
923+
target = xtables._xtables_find_target(name, XTF_TRY_LOAD)
924+
if not target:
925+
return target
926+
self._loaded(name)
792927
version = xtables._get_xtables_version(target.contents.v1.version)
793928

794929
if 1 == version:
@@ -810,6 +945,7 @@ def find_target(self, name):
810945
else:
811946
raise Exception("Target object casting failed")
812947

948+
@preserve_globals
813949
def save(self, module, ip, ptr):
814950
_wrap_save(module.save, ct.cast(ct.pointer(ip), ct.c_void_p), ptr)
815951

@@ -835,6 +971,7 @@ def _parse(self, module, argv, inv, flags, entry, ptr):
835971

836972
# Dispatch arguments to the appropriate parse function, based upon the
837973
# extension's choice of API.
974+
@preserve_globals
838975
def parse_target(self, argv, invert, t, fw, ptr):
839976
_optarg.value = argv[1]
840977
_optind.value = 2
@@ -879,6 +1016,7 @@ def parse_target(self, argv, invert, t, fw, ptr):
8791016

8801017
# Dispatch arguments to the appropriate parse function, based upon the
8811018
# extension's choice of API.
1019+
@preserve_globals
8821020
def parse_match(self, argv, invert, m, fw, ptr):
8831021
_optarg.value = argv[1]
8841022
_optind.value = 2
@@ -936,6 +1074,7 @@ def _options_fcheck(self, name, xflags, table):
9361074

9371075
# Dispatch arguments to the appropriate final_check function, based upon
9381076
# the extension's choice of API.
1077+
@preserve_globals
9391078
def final_check_target(self, target):
9401079
x6_fcheck = None
9411080
try:
@@ -973,6 +1112,7 @@ def final_check_target(self, target):
9731112

9741113
# Dispatch arguments to the appropriate final_check function, based upon
9751114
# the extension's choice of API.
1115+
@preserve_globals
9761116
def final_check_match(self, match):
9771117
x6_fcheck = None
9781118
try:

libxtwrapper/wrapper.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,17 @@ int wrap_uintfn(void (*fn)(unsigned int), unsigned int data)
8080

8181
return 0;
8282
}
83+
84+
int wrap_voidfn(void (*fn)(void))
85+
{
86+
int err;
87+
88+
if ((err = setjmp(env)) == 0) {
89+
fn();
90+
} else {
91+
errno = err;
92+
return -err;
93+
}
94+
95+
return 0;
96+
}

0 commit comments

Comments
 (0)