mirror of
				https://github.com/python/cpython.git
				synced 2025-10-27 03:34:32 +00:00 
			
		
		
		
	bpo-39991: Enhance uuid parser for MAC address (GH-19045)
Reject valid IPv6 addresses which doesn't contain "::" but have a length of 17 characters.
This commit is contained in:
		
							parent
							
								
									5b1ef200d3
								
							
						
					
					
						commit
						ebf6bb9f5e
					
				
					 2 changed files with 93 additions and 31 deletions
				
			
		|  | @ -679,6 +679,58 @@ class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase): | |||
| class BaseTestInternals: | ||||
|     _uuid = py_uuid | ||||
| 
 | ||||
|     def check_parse_mac(self, aix): | ||||
|         if not aix: | ||||
|             patch = mock.patch.multiple(self.uuid, | ||||
|                                         _MAC_DELIM=b':', | ||||
|                                         _MAC_OMITS_LEADING_ZEROES=False) | ||||
|         else: | ||||
|             patch = mock.patch.multiple(self.uuid, | ||||
|                                         _MAC_DELIM=b'.', | ||||
|                                         _MAC_OMITS_LEADING_ZEROES=True) | ||||
| 
 | ||||
|         with patch: | ||||
|             # Valid MAC addresses | ||||
|             if not aix: | ||||
|                 tests = ( | ||||
|                     (b'52:54:00:9d:0e:67', 0x5254009d0e67), | ||||
|                     (b'12:34:56:78:90:ab', 0x1234567890ab), | ||||
|                 ) | ||||
|             else: | ||||
|                 # AIX format | ||||
|                 tests = ( | ||||
|                     (b'fe.ad.c.1.23.4', 0xfead0c012304), | ||||
|                 ) | ||||
|             for mac, expected in tests: | ||||
|                 self.assertEqual(self.uuid._parse_mac(mac), expected) | ||||
| 
 | ||||
|             # Invalid MAC addresses | ||||
|             for mac in ( | ||||
|                 b'', | ||||
|                 # IPv6 addresses with same length than valid MAC address | ||||
|                 # (17 characters) | ||||
|                 b'fe80::5054:ff:fe9', | ||||
|                 b'123:2:3:4:5:6:7:8', | ||||
|                 # empty 5rd field | ||||
|                 b'52:54:00:9d::67', | ||||
|                 # only 5 fields instead of 6 | ||||
|                 b'52:54:00:9d:0e' | ||||
|                 # invalid character 'x' | ||||
|                 b'52:54:00:9d:0e:6x' | ||||
|                 # dash separator | ||||
|                 b'52-54-00-9d-0e-67', | ||||
|             ): | ||||
|                 if aix: | ||||
|                     mac = mac.replace(b':', b'.') | ||||
|                 with self.subTest(mac=mac): | ||||
|                     self.assertIsNone(self.uuid._parse_mac(mac)) | ||||
| 
 | ||||
|     def test_parse_mac(self): | ||||
|         self.check_parse_mac(False) | ||||
| 
 | ||||
|     def test_parse_mac_aix(self): | ||||
|         self.check_parse_mac(True) | ||||
| 
 | ||||
|     def test_find_under_heading(self): | ||||
|         data = '''\ | ||||
| Name  Mtu   Network     Address           Ipkts Ierrs    Opkts Oerrs  Coll | ||||
|  |  | |||
							
								
								
									
										64
									
								
								Lib/uuid.py
									
										
									
									
									
								
							
							
						
						
									
										64
									
								
								Lib/uuid.py
									
										
									
									
									
								
							|  | @ -434,6 +434,34 @@ def _find_mac_near_keyword(command, args, keywords, get_word_index): | |||
|     return first_local_mac or None | ||||
| 
 | ||||
| 
 | ||||
| def _parse_mac(word): | ||||
|     # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'), | ||||
|     # but reject IPv6 address (ex: 'fe80::5054:ff:fe9' or '123:2:3:4:5:6:7:8'). | ||||
|     # | ||||
|     # Virtual interfaces, such as those provided by VPNs, do not have a | ||||
|     # colon-delimited MAC address as expected, but a 16-byte HWAddr separated | ||||
|     # by dashes. These should be ignored in favor of a real MAC address | ||||
|     parts = word.split(_MAC_DELIM) | ||||
|     if len(parts) != 6: | ||||
|         return | ||||
|     if _MAC_OMITS_LEADING_ZEROES: | ||||
|         # (Only) on AIX the macaddr value given is not prefixed by 0, e.g. | ||||
|         # en0   1500  link#2      fa.bc.de.f7.62.4 110854824     0 160133733     0     0 | ||||
|         # not | ||||
|         # en0   1500  link#2      fa.bc.de.f7.62.04 110854824     0 160133733     0     0 | ||||
|         if not all(1 <= len(part) <= 2 for part in parts): | ||||
|             return | ||||
|         hexstr = b''.join(part.rjust(2, b'0') for part in parts) | ||||
|     else: | ||||
|         if not all(len(part) == 2 for part in parts): | ||||
|             return | ||||
|         hexstr = b''.join(parts) | ||||
|     try: | ||||
|         return int(hexstr, 16) | ||||
|     except ValueError: | ||||
|         return | ||||
| 
 | ||||
| 
 | ||||
| def _find_mac_under_heading(command, args, heading): | ||||
|     """Looks for a MAC address under a heading in a command's output. | ||||
| 
 | ||||
|  | @ -453,39 +481,21 @@ def _find_mac_under_heading(command, args, heading): | |||
| 
 | ||||
|     first_local_mac = None | ||||
|     for line in stdout: | ||||
|         try: | ||||
|         words = line.rstrip().split() | ||||
|         try: | ||||
|             word = words[column_index] | ||||
|             # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'), | ||||
|             # but reject IPv6 address (ex: 'fe80::5054:ff:fe9') detected | ||||
|             # by '::' pattern. | ||||
|             if len(word) == 17 and b'::' not in word: | ||||
|                 mac = int(word.replace(_MAC_DELIM, b''), 16) | ||||
|             elif _MAC_OMITS_LEADING_ZEROES: | ||||
|                 # (Only) on AIX the macaddr value given is not prefixed by 0, e.g. | ||||
|                 # en0   1500  link#2      fa.bc.de.f7.62.4 110854824     0 160133733     0     0 | ||||
|                 # not | ||||
|                 # en0   1500  link#2      fa.bc.de.f7.62.04 110854824     0 160133733     0     0 | ||||
|                 parts = word.split(_MAC_DELIM) | ||||
|                 if len(parts) == 6 and all(0 < len(p) <= 2 for p in parts): | ||||
|                     hexstr = b''.join(p.rjust(2, b'0') for p in parts) | ||||
|                     mac = int(hexstr, 16) | ||||
|                 else: | ||||
|         except IndexError: | ||||
|             continue | ||||
|             else: | ||||
| 
 | ||||
|         mac = _parse_mac(word) | ||||
|         if mac is None: | ||||
|             continue | ||||
|         except (ValueError, IndexError): | ||||
|             # Virtual interfaces, such as those provided by | ||||
|             # VPNs, do not have a colon-delimited MAC address | ||||
|             # as expected, but a 16-byte HWAddr separated by | ||||
|             # dashes. These should be ignored in favor of a | ||||
|             # real MAC address | ||||
|             pass | ||||
|         else: | ||||
|         if _is_universal(mac): | ||||
|             return mac | ||||
|             first_local_mac = first_local_mac or mac | ||||
|     return first_local_mac or None | ||||
|         if first_local_mac is None: | ||||
|             first_local_mac = mac | ||||
| 
 | ||||
|     return first_local_mac | ||||
| 
 | ||||
| 
 | ||||
| # The following functions call external programs to 'get' a macaddr value to | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Victor Stinner
						Victor Stinner