@@ -153,42 +153,21 @@ def _is_builtin_open_for_writing(node):
153153 return False
154154
155155
156- def _is_allowed_mode (node , allowed_modes ):
156+ def _get_mode_arg (node , args_idx ):
157157 mode = None
158- if len (node .args ) > 1 and isinstance (node .args [1 ], astroid .Const ):
159- mode = node .args [1 ].value
158+ if len (node .args ) > args_idx and isinstance (node .args [args_idx ], astroid .Const ):
159+ mode = node .args [args_idx ].value
160160 elif node .keywords :
161161 for keyword in node .keywords :
162162 if keyword .arg == 'mode' and isinstance (keyword .value , astroid .Const ):
163163 mode = keyword .value .value
164164 break
165- if mode is not None :
166- return mode in allowed_modes
167-
168- # NB: default to True in all other cases
169- return True
165+ return mode
170166
171167
172- def _is_os_open_allowed_mode (node , allowed_modes ):
173- mode = None
174- flags = None # pylint: disable=unused-variable
175- if len (node .args ) > 1 and isinstance (node .args [1 ], (astroid .Attribute , astroid .BinOp )):
176- # Cover:
177- # * os.open(xxx, os.O_WRONLY)
178- # * os.open(xxx, os.O_WRONLY | os.O_CREATE)
179- # * os.open(xxx, os.O_WRONLY | os.O_CREATE | os.O_FSYNC)
180- flags = node .args [1 ]
181- if len (node .args ) > 2 and isinstance (node .args [2 ], astroid .Const ):
182- mode = node .args [2 ].value
183- elif node .keywords :
184- for keyword in node .keywords :
185- if keyword .arg == 'flags' and isinstance (keyword .value , (astroid .Attribute , astroid .BinOp )):
186- flags = keyword .value # pylint: disable=unused-variable # noqa: F841
187- if keyword .arg == 'mode' and isinstance (keyword .value , astroid .Const ):
188- mode = keyword .value .value
189- break
168+ def _is_allowed_mode (node , allowed_modes , args_idx ):
169+ mode = _get_mode_arg (node , args_idx = args_idx )
190170 if mode is not None :
191- # TODO: condition check on the flags value if present (ie. ignore if read-only)
192171 return mode in allowed_modes
193172
194173 # NB: default to True in all other cases
@@ -303,14 +282,10 @@ def _is_yaml_unsafe_call(node):
303282# ==============================================================================
304283
305284
306- class SecureCodingStandardChecker (BaseChecker ):
285+ class SecureCodingStandardChecker (BaseChecker ): # pylint: disable=too-many-instance-attributes
307286 """Plugin class."""
308287
309288 DEFAULT_MAX_MODE = 0o755
310- W8012_DISPLAY_MSG = 'Avoid using `os.open` with unsafe permissions'
311- W8016_DISPLAY_MSG = 'Avoid using `os.mkdir` and `os.makedirs` with unsafe permissions'
312- W8017_DISPLAY_MSG = 'Avoid using `os.mkfifo` with unsafe permissions'
313- W8018_DISPLAY_MSG = 'Avoid using `os.mknod` with unsafe permissions'
314289
315290 __implements__ = (IAstroidChecker ,)
316291
@@ -420,7 +395,7 @@ class SecureCodingStandardChecker(BaseChecker):
420395 'Use of `shlex.quote()` should be avoided on non-POSIX platforms (such as Windows)' ,
421396 ),
422397 'W8012' : (
423- W8012_DISPLAY_MSG ,
398+ 'Avoid using `os.open` with unsafe permissions (should be %s)' ,
424399 'os-open-unsafe-permissions' ,
425400 'Avoid using `os.open` with unsafe file permissions (by default 0 <= mode <= 0o755)' ,
426401 ),
@@ -440,29 +415,32 @@ class SecureCodingStandardChecker(BaseChecker):
440415 'Use of `shelve.open()` should be avoided in favour of safer file formats' ,
441416 ),
442417 'W8016' : (
443- W8016_DISPLAY_MSG ,
418+ 'Avoid using `os.mkdir` and `os.makedirs` with unsafe permissions (should be %s)' ,
444419 'os-mkdir-unsafe-permissions' ,
445420 'Avoid using `os.mkdir` and `os.makedirs` with unsafe file permissions (by default 0 <= mode <= 0o755)' ,
446421 ),
447422 'W8017' : (
448- W8017_DISPLAY_MSG ,
423+ 'Avoid using `os.mkfifo` with unsafe permissions (should be %s)' ,
449424 'os-mkfifo-unsafe-permissions' ,
450425 'Avoid using `os.mkfifo` with unsafe file permissions (by default 0 <= mode <= 0o755)' ,
451426 ),
452427 'W8018' : (
453- W8018_DISPLAY_MSG ,
428+ 'Avoid using `os.mknod` with unsafe permissions (should be %s)' ,
454429 'os-mknod-unsafe-permissions' ,
455430 'Avoid using `os.mknod` with unsafe file permissions (by default 0 <= mode <= 0o755)' ,
456431 ),
457432 }
458433
459- def __init__ (self , * args , ** kwargs ):
434+ def __init__ (self , linter ):
460435 """Initialize a SecureCodingStandardChecker object."""
461- super ().__init__ (* args , ** kwargs )
462- self ._prefer_os_open = False
436+ super ().__init__ (linter )
437+ self ._os_open_msg_arg = ''
463438 self ._os_open_modes_allowed = []
439+ self ._os_mkdir_msg_arg = ''
464440 self ._os_mkdir_modes_allowed = []
441+ self ._os_mkfifo_msg_arg = ''
465442 self ._os_mkfifo_modes_allowed = []
443+ self ._os_mknod_msg_arg = ''
466444 self ._os_mknod_modes_allowed = []
467445
468446 def visit_call (self , node ): # pylint: disable=too-many-branches
@@ -483,18 +461,18 @@ def visit_call(self, node): # pylint: disable=too-many-branches
483461 self .add_message ('avoid-shell-true' , node = node )
484462 elif _is_function_call (node , module = 'os' , function = 'popen' ):
485463 self .add_message ('avoid-os-popen' , node = node )
486- elif _is_builtin_open_for_writing (node ) and self ._prefer_os_open :
464+ elif _is_builtin_open_for_writing (node ) and self ._os_open_modes_allowed :
487465 self .add_message ('replace-builtin-open' , node = node )
488466 elif isinstance (node .func , astroid .Name ) and (node .func .name in ('eval' , 'exec' )):
489467 self .add_message ('avoid-eval-exec' , node = node )
490468 elif not _is_posix () and _is_function_call (node , module = 'shlex' , function = 'quote' ):
491469 self .add_message ('avoid-shlex-quote-on-non-posix' , node = node )
492470 elif (
493471 _is_function_call (node , module = 'os' , function = 'open' )
494- and self ._prefer_os_open
495- and not _is_os_open_allowed_mode (node , self ._os_open_modes_allowed )
472+ and self ._os_open_modes_allowed
473+ and not _is_allowed_mode (node , self ._os_open_modes_allowed , args_idx = 2 )
496474 ):
497- self .add_message ('os-open-unsafe-permissions' , node = node )
475+ self .add_message ('os-open-unsafe-permissions' , node = node , args = ( self . _os_open_msg_arg ,) )
498476 elif _is_function_call (node , module = 'pickle' , function = ('load' , 'loads' )):
499477 self .add_message ('avoid-pickle-load' , node = node )
500478 elif _is_function_call (node , module = 'marshal' , function = ('load' , 'loads' )):
@@ -505,21 +483,21 @@ def visit_call(self, node): # pylint: disable=too-many-branches
505483 if (
506484 _is_function_call (node , module = 'os' , function = ('mkdir' , 'makedirs' ))
507485 and self ._os_mkdir_modes_allowed
508- and not _is_allowed_mode (node , self ._os_mkdir_modes_allowed )
486+ and not _is_allowed_mode (node , self ._os_mkdir_modes_allowed , args_idx = 1 )
509487 ):
510- self .add_message ('os-mkdir-unsafe-permissions' , node = node )
488+ self .add_message ('os-mkdir-unsafe-permissions' , node = node , args = ( self . _os_mkdir_msg_arg ,) )
511489 elif (
512490 _is_function_call (node , module = 'os' , function = 'mkfifo' )
513491 and self ._os_mkfifo_modes_allowed
514- and not _is_allowed_mode (node , self ._os_mkfifo_modes_allowed )
492+ and not _is_allowed_mode (node , self ._os_mkfifo_modes_allowed , args_idx = 1 )
515493 ):
516- self .add_message ('os-mkfifo-unsafe-permissions' , node = node )
494+ self .add_message ('os-mkfifo-unsafe-permissions' , node = node , args = ( self . _os_mkfifo_msg_arg ,) )
517495 elif (
518496 _is_function_call (node , module = 'os' , function = 'mknod' )
519497 and self ._os_mknod_modes_allowed
520- and not _is_allowed_mode (node , self ._os_mknod_modes_allowed )
498+ and not _is_allowed_mode (node , self ._os_mknod_modes_allowed , args_idx = 1 )
521499 ):
522- self .add_message ('os-mknod-unsafe-permissions' , node = node )
500+ self .add_message ('os-mknod-unsafe-permissions' , node = node , args = ( self . _os_mknod_msg_arg ,) )
523501
524502 def visit_import (self , node ):
525503 """Visitor method called for astroid.Import nodes."""
@@ -560,11 +538,11 @@ def visit_with(self, node):
560538 """Visitor method called for astroid.With nodes."""
561539 for item in node .items :
562540 if item and isinstance (item [0 ], astroid .Call ):
563- if self ._prefer_os_open :
541+ if self ._os_open_modes_allowed :
564542 if _is_builtin_open_for_writing (item [0 ]):
565543 self .add_message ('replace-builtin-open' , node = node )
566- elif _is_function_call (item [0 ], module = 'os' , function = 'open' ) and not _is_os_open_allowed_mode (
567- item [0 ], self ._os_open_modes_allowed
544+ elif _is_function_call (item [0 ], module = 'os' , function = 'open' ) and not _is_allowed_mode (
545+ item [0 ], self ._os_open_modes_allowed , args_idx = 2
568546 ):
569547 self .add_message ('os-open-unsafe-permissions' , node = node )
570548 elif _is_function_call (item [0 ], module = 'shelve' , function = 'open' ):
@@ -574,55 +552,30 @@ def visit_assert(self, node):
574552 """Visitor method called for astroid.Assert nodes."""
575553 self .add_message ('avoid-assert' , node = node )
576554
577- def set_os_open_mode (self , value ):
578- """
579- Control whether we prefer `os.open` over the builtin `open`.
580-
581- Args:
582- value (str): Option value
583- """
555+ def _set_mode_option (self , config_name , name , value ):
556+ modes = _read_octal_mode_option (config_name , value , self .DEFAULT_MAX_MODE )
584557
585- def _update_display_msg (suffix = '' ):
586- self .msg ['W8012' ] = (self .W8012_DISPLAY_MSG + suffix , self .msg ['W8012' ][1 ], self .msg ['W8012' ][2 ])
587-
588- modes = _read_octal_mode_option ('os_open_mode' , value , list (range (0 , self .DEFAULT_MAX_MODE + 1 )))
589-
590- self ._os_open_modes_allowed .clear ()
591-
592- if isinstance (modes , int ):
593- self ._prefer_os_open = modes > 0
594- if self ._prefer_os_open :
595- self ._os_open_modes_allowed = list (range (0 , modes + 1 ))
596- _update_display_msg (suffix = f' (mode <= { value } )' )
597- elif not modes :
598- self ._prefer_os_open = False
599- else :
600- self ._prefer_os_open = True
601- self ._os_open_modes_allowed = modes
602- _update_display_msg (suffix = f' (mode in { modes } )' )
603-
604- def _set_mode_option (self , config_name , name , value , warning_id ):
605- def _update_display_msg (suffix = '' ):
606- self .msg [warning_id ] = (
607- getattr (self , f'{ warning_id } _DISPLAY_MSG' ) + suffix ,
608- self .msg [warning_id ][1 ],
609- self .msg [warning_id ][2 ],
610- )
611-
612- modes = _read_octal_mode_option (config_name , value , list (range (0 , self .DEFAULT_MAX_MODE + 1 )))
613-
614- if isinstance (modes , int ):
615- if modes > 0 :
616- setattr (self , f'_os_{ name } _modes_allowed' , list (range (0 , modes + 1 )))
617- _update_display_msg (suffix = f' (mode <= { value } )' )
618- else :
619- getattr (self , f'_os_{ name } _modes_allowed' ).clear ()
558+ if isinstance (modes , int ) and modes > 0 :
559+ setattr (self , f'_os_{ name } _modes_allowed' , list (range (0 , modes + 1 )))
560+ setattr (self , f'_os_{ name } _msg_arg' , f'0 < mode < { oct (modes )} ' )
620561 elif modes :
621562 setattr (self , f'_os_{ name } _modes_allowed' , modes )
622- _update_display_msg ( suffix = f' ( mode in { modes } ) ' )
563+ setattr ( self , f'_os_ { name } _msg_arg' , f' mode in { [ oct ( mode ) for mode in modes ] } ' )
623564 else :
624565 getattr (self , f'_os_{ name } _modes_allowed' ).clear ()
625566
567+ def set_os_open_allowed_modes (self , value ):
568+ """
569+ Set the allowed modes for `os.open`.
570+
571+ Note:
572+ This option has no effect on non-POSIX platforms.
573+
574+ Args:
575+ value (str): Option value
576+ """
577+ self ._set_mode_option ('os_open_mode' , 'open' , value )
578+
626579 def set_os_mkdir_allowed_modes (self , value ):
627580 """
628581 Set the allowed modes for `os.mkdir` and `os.makedirs`.
@@ -633,7 +586,7 @@ def set_os_mkdir_allowed_modes(self, value):
633586 Args:
634587 value (str): Option value
635588 """
636- self ._set_mode_option ('os_mkdir_mode' , 'mkdir' , value , 'W8016' )
589+ self ._set_mode_option ('os_mkdir_mode' , 'mkdir' , value )
637590
638591 def set_os_mkfifo_allowed_modes (self , value ):
639592 """
@@ -645,7 +598,7 @@ def set_os_mkfifo_allowed_modes(self, value):
645598 Args:
646599 value (str): Option value
647600 """
648- self ._set_mode_option ('os_mkfifo_mode' , 'mkfifo' , value , 'W8017' )
601+ self ._set_mode_option ('os_mkfifo_mode' , 'mkfifo' , value )
649602
650603 def set_os_mknod_allowed_modes (self , value ):
651604 """
@@ -657,7 +610,7 @@ def set_os_mknod_allowed_modes(self, value):
657610 Args:
658611 value (str): Option value
659612 """
660- self ._set_mode_option ('os_mknod_mode' , 'mknod' , value , 'W8018' )
613+ self ._set_mode_option ('os_mknod_mode' , 'mknod' , value )
661614
662615
663616def register (linter ): # pragma: no cover
@@ -669,8 +622,7 @@ def load_configuration(linter): # pragma: no cover
669622 """Load data from the configuration file."""
670623 for checker in linter .get_checkers ():
671624 if isinstance (checker , SecureCodingStandardChecker ):
672- checker .set_os_open_mode (checker .config .os_open_mode )
673- checker .set_os_open_mode (checker .config .os_mkdir_mode )
674- checker .set_os_open_mode (checker .config .os_mkfifo_mode )
675- checker .set_os_open_mode (checker .config .os_mknod_mode )
676- break
625+ checker .set_os_open_allowed_modes (checker .config .os_open_mode )
626+ checker .set_os_mkdir_allowed_modes (checker .config .os_mkdir_mode )
627+ checker .set_os_mkfifo_allowed_modes (checker .config .os_mkfifo_mode )
628+ checker .set_os_mknod_allowed_modes (checker .config .os_mknod_mode )
0 commit comments