New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-99127: Port syslog module to use module state. #99128
base: main
Are you sure you want to change the base?
Conversation
corona10
commented
Nov 5, 2022
•
edited by bedevere-bot
edited by bedevere-bot
- Issue: Port syslog module to use module state #99127
(.oss) ➜ cpython git:(gh-99127) ✗ ./python.exe -m test test_syslog -R 3:3
Raised RLIMIT_NOFILE: 256 -> 1024
0:00:00 load avg: 3.65 Run tests sequentially
0:00:00 load avg: 3.65 [1/1] test_syslog
beginning 6 repetitions
123456
......
== Tests result: SUCCESS ==
1 test OK.
Total duration: 704 ms
Tests result: SUCCESS |
Something to consider: what happens when multiple sub interpreters use the syslog extension. The current implementation always calls openlog(3) at least once before calling syslog(3), see the check in syslog_syslog_impl.
That results in interference between sub interpreters when in one of them the python code explicitly calls syslog.openlog and the other doesn't.
IMHO the check in syslog_syslog_impl is not necessary because it results in a call to openlog(3) with parameters that match the default behaviour of the syslog library. Removing it is a fairly minor behaviour change though (syslog.syslog will currently override any calls to openlog that were done in C before the first call to syslog.syslog and will stop doing that when removing the check in syslog_syslog_impl).
| _syslog_clear(PyObject *module) | ||
| { | ||
| _syslog_state *state = get_syslog_state(module); | ||
| Py_CLEAR(state->S_ident_o); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementations of openlog(3) might just store the "ident" argument (which is why S_ident_o exists). On such implementations the Py_CLEAR call results into use-after-free when syslog() is called from C code after this point.
Probably best to call closelog(3) with some default arguments before clearing.
Thanks that's why I got a headache which makes me close the issue but thanks to suggest the solution.
Yeah, IIUC we should call openlog(3) anyway while calling syslog.syslog right? |
Or did you intended implicit call of openlog(3) while calling syslog(3)? |
|
Failure tests from buildbot/PPC64LE Fedora were not related to this change. |
Modules/syslogmodule.c
Outdated
| static void | ||
| _syslog_free(void *module) | ||
| { | ||
| _syslog_clear((PyObject *)module); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| _syslog_clear((PyObject *)module); | |
| (void)_syslog_clear((PyObject *)module); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why casting is needed at this moment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some compilers like clang complain if the returned value is ignored and it is best practice to add cast to ignore the returned value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some compilers like clang complain if the returned value is ignored
Please let me know which clang version emits the warning this case.
FYI I am using clang 14.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please let me know which clang version emits the warning this case.
I don't remember the version and the compiler option since I use gcc on linux not clang. There are occurrences of this in stdlib like
cpython/Modules/_functoolsmodule.c
Line 189 in 61b6c40
| (void)partial_clear(pto); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah but some code doesn't use the casting also.
Line 628 in 61b6c40
| scanner_clear((PyScannerObject *)self); |
But let's follow the recent convention, I can't find related issues about the casting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Casting should not be necessary here and is just clutter. Clang and GCC can warn about ignored return values, but that's only when APIs are marked with a warn_unused_result attribute (which at least some libraries use to mark functions where the result is an error indicator that must be checked).
|
Unfortunately I don't have time to review this right now. Maybe @serhiy-storchaka can have a look? He recently did some work on this module. |
At the C level calling openlog(3) before calling syslog(3) is not necessary, although the spec is not entirely clear about this. The linux manual page does clearly mention that openlog(3) is optional, and matches what I remember from older Unix systems. The call to openlog in syslog_syslog_impl basically resets the default system state (use the process name for the ident and log using the LOG_USER facility). Leaving that out should therefore be harmless, but this is technically a user-visible change of behaviour:
Currently the Python code resets the values set in the first step, with my proposal it would no longer do this. I'd consider this a good change, but there's bound to be someone affected by this. |
| static char S_log_open = 0; | ||
| typedef struct { | ||
| PyObject *S_ident_o; /* identifier, held by openlog() */ | ||
| char S_log_open; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the S_ prefix stand for? Does it mean "state"? Maybe remove it?
| static char S_log_open = 0; | ||
| typedef struct { | ||
| PyObject *S_ident_o; /* identifier, held by openlog() */ | ||
| char S_log_open; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a comment to explain the purpose of the S_log_open flag? I understand that it's set to 1 after openlog() is called.
| @@ -162,8 +170,9 @@ syslog_openlog_impl(PyObject *module, PyObject *ident, long logopt, | |||
| } | |||
|
|
|||
| openlog(ident_str, logopt, facility); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API is stateless. What happens if interpreter A calls openlog("idA", options_a, LONG_USER) and interpreter B calls openlog("idB", options_b, LOG_INFO)?
I'm not sure if it's a good idea to make the global S_log_open flag per-interpreter. See for example the recent crash on macOS: #98178. Same question for S_ident_o.
If you're confident that safe, please add a comment explaining the behavior, who owns the memory, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is safe, but the behavior might be unexpected. The only issue of safety would be how POSIX syslog keeps a pointer to "ident" that must not be deallocated before closelog() is called. I don't see where that would be a problem.
That said, moving the data to per-interpreter module state may lead to a slight change in behavior. Again, though, using syslog with multiple interpreters is already wierd.
| { | ||
| _syslog_state *state = get_syslog_state(module); | ||
| if (state->S_log_open) { | ||
| closelog(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if interpreter A calls closelog() and interpreter B (same process!) still uses the Python syslog module, so after closelog() has been called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POSIX syslog will transparently call openlog() with default values.
|
When you're done making the requested changes, leave the comment: And if you don't make the requested changes, you will be poked with soft cushions! |
|
I've posted to the issue about interpreters interfering with each other (which is not new with this PR): #99127 (comment). |
|
For the specific case of sub-interpreters, maybe sub-interpreters must not be allowed to call openlog() and closelog()? But for the simple case of a single intereterpreter but loading the C extensions twice, there is a similar issue. This problem reminds me the complicated case of the posix/nt extension being converted to multi-phase init. I got a very complicated issue about Python exit. Some applications started to crash. Oops. Python os.environ[key] = value stored a copy of "key=value" string... until Python exit. But if the application still runs after Python shutdown, reading the environment could lead to reading freed memory which leaded to a crash! See:
The fix? Let the C library handles memory for us: use setenv() function, rather than putenv()! For me, the Python syslog module should be a thin wrapper to a glue between Python and the underlying C library: have something which handles openlog() / closelog(), keep memory around, and only frees this memory when the last interpreter is deleted. Maybe it can be a shared state with a reference counter?
For me, it sounds weird and dangerous to still use closelog() in another syslog instance which remains alive. Maybe it works on some operating systems. But again, see the recent crash on macOS. |
|
For example, the Python |
|
Yeah, it is usually best to restrict management of global resources to the main interpreter. I'd say the syslog API counts as a global resource, so I'd be good with |
|
FYI, it looks like |