Skip to content
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

Timemgr using sprintf with the output string also used in the input #284

Closed
oehmke opened this issue Sep 3, 2024 · 5 comments · Fixed by #298
Closed

Timemgr using sprintf with the output string also used in the input #284

oehmke opened this issue Sep 3, 2024 · 5 comments · Fixed by #298
Assignees

Comments

@oehmke
Copy link
Contributor

oehmke commented Sep 3, 2024

Atanas ran into an issue with a garbled time string and traced it down to a sprintf call in the time manager. It looks like there are several instances of sprintf() with the output string being also used as an input. It sounds like in general this shouldn't be done, but that it sometimes works (depending on the implementation of sprintf()). We should fix all of these, so we don't run into random issues. An example of one of these is in .../TimeMgr/src/ESMCI_Time.C line 1482.

@billsacks
Copy link
Member

I'm happy to take this on.

I just did a regex search to try to identify the places that need to be fixed. This would miss anything where a sprintf call is split across multiple lines, but hopefully is at least a good start: The regex is sprintf *\(([^,]+) *,.*\1. There are a number of false positives, but it picks up some things that should be fixed:

sprintf(msg,"%selem Id: %d clip_elem Id: %d\n", msg, elem.get_id(), clip_elem.get_id());

for(int npt=0; npt<num_p*2; npt++) sprintf(msg,"%s %g", msg, cd_sph[npt]);

sprintf(timeString, "%s%012.9f", timeString, (s + fractionalSeconds));

sprintf(timeString, "%s%02d", timeString, s);

sprintf(timeString, "%s%02d:%lld/%lld", timeString, s, sN, sD);

sprintf(timeString, "%s%02d", timeString, s);

sprintf(timeString, "%s%.9fS", timeString, (s + fractionalSeconds));

sprintf(timeString, "%s%dS", timeString, s);

sprintf(timeString, "%s%d:%lld/%lldS", timeString, s, sN, sD);

sprintf(timeString, "%s%dS", timeString, s);

@oehmke
Copy link
Contributor Author

oehmke commented Sep 3, 2024 via email

@billsacks
Copy link
Member

I've started looking at this. @oehmke I'd like some confirmation on what I'm thinking here:

I'm thinking that, ideally, we should add an argument to these two functions:

int Time::getString(
//
// !RETURN VALUE:
// int error return code
//
// !ARGUMENTS:
char *timeString, const char *options) const { // out - time value in

int TimeInterval::getString(
//
// !RETURN VALUE:
// int error return code
//
// !ARGUMENTS:
char *timeString, const char *options) const { // out - time interval

giving the string length. It looks to me like we have that length available in all calls; for example:

int timeStringLen, // in - F90 time string size

which is referenced here:

if (tempTimeString != ESMC_NULL_POINTER && timeStringLen > 0) {
rc = Time::getString(tempTimeString);

But I'm not familiar enough with the Fortran-C connections in ESMF to be confident about this: can we rely on this timeStringLen variable being the size of the C character allocation for tempTimeString? Or maybe off-by-one because of the need to hold the terminating null character on the C side?? In case it's helpful, this is the relevant Fortran-C call:

void FTN_X(c_esmc_timeget)(Time *ptr,
ESMC_I4 *yy, ESMC_I8 *yy_i8,
int *mm, int *dd,
ESMC_I4 *d, ESMC_I8 *d_i8,
ESMC_I4 *h, ESMC_I4 *m,
ESMC_I4 *s, ESMC_I8 *s_i8,
ESMC_I4 *ms, ESMC_I4 *us,
ESMC_I4 *ns,
ESMC_R8 *d_r8, ESMC_R8 *h_r8,
ESMC_R8 *m_r8, ESMC_R8 *s_r8,
ESMC_R8 *ms_r8, ESMC_R8 *us_r8,
ESMC_R8 *ns_r8,
ESMC_I4 *sN, ESMC_I8 *sN_i8,
ESMC_I4 *sD, ESMC_I8 *sD_i8,
Calendar **calendar,
ESMC_CalKind_Flag *calkindflag,
int *timeZone,
int *timeStringLen, int *tempTimeStringLen,
char *tempTimeString,
int *timeStringLenISOFrac,
int *tempTimeStringLenISOFrac,
char *tempTimeStringISOFrac,
int *dayOfWeek,
Time *midMonth,
ESMC_I4 *dayOfYear,
ESMC_R8 *dayOfYear_r8,
TimeInterval *dayOfYear_intvl,
int *status,
ESMCI_FortranStrLenArg tempTime_l,
ESMCI_FortranStrLenArg tempTimeISOFrac_l) {
int rc = (ptr)->Time::get(
ESMC_NOT_PRESENT_FILTER(yy),
ESMC_NOT_PRESENT_FILTER(yy_i8),
ESMC_NOT_PRESENT_FILTER(mm),
ESMC_NOT_PRESENT_FILTER(dd),
ESMC_NOT_PRESENT_FILTER(d),
ESMC_NOT_PRESENT_FILTER(d_i8),
ESMC_NOT_PRESENT_FILTER(h),
ESMC_NOT_PRESENT_FILTER(m),
ESMC_NOT_PRESENT_FILTER(s),
ESMC_NOT_PRESENT_FILTER(s_i8),
ESMC_NOT_PRESENT_FILTER(ms),
ESMC_NOT_PRESENT_FILTER(us),
ESMC_NOT_PRESENT_FILTER(ns),
ESMC_NOT_PRESENT_FILTER(d_r8),
ESMC_NOT_PRESENT_FILTER(h_r8),
ESMC_NOT_PRESENT_FILTER(m_r8),
ESMC_NOT_PRESENT_FILTER(s_r8),
ESMC_NOT_PRESENT_FILTER(ms_r8),
ESMC_NOT_PRESENT_FILTER(us_r8),
ESMC_NOT_PRESENT_FILTER(ns_r8),
ESMC_NOT_PRESENT_FILTER(sN),
ESMC_NOT_PRESENT_FILTER(sN_i8),
ESMC_NOT_PRESENT_FILTER(sD),
ESMC_NOT_PRESENT_FILTER(sD_i8),
ESMC_NOT_PRESENT_FILTER(calendar),
ESMC_NOT_PRESENT_FILTER(calkindflag),
ESMC_NOT_PRESENT_FILTER(timeZone),
// always present internal arguments
*timeStringLen,
tempTimeStringLen,
tempTimeString,
*timeStringLenISOFrac,
tempTimeStringLenISOFrac,
tempTimeStringISOFrac,
ESMC_NOT_PRESENT_FILTER(dayOfWeek),
ESMC_NOT_PRESENT_FILTER(midMonth),
ESMC_NOT_PRESENT_FILTER(dayOfYear),
ESMC_NOT_PRESENT_FILTER(dayOfYear_r8),
ESMC_NOT_PRESENT_FILTER(dayOfYear_intvl) );
if (ESMC_PRESENT(status)) *status = rc;

My sense is that these getString methods are only accessible internally, so it's okay to change the interface... Time::getString is a public method and appears like this:

//-------------------------------------------------------------------------
// Public methods to support Time::get() API
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//BOP
// !IROUTINE: Time::getString - Get a Time value
//
// !INTERFACE:
int Time::getString(

but my impression is that this C++ code isn't actually part of the ESMF public API; is that true?

Let me know if you'd find it easier to meet to talk about this rather than trying to reply in writing. I have other things I can work on while waiting for a reply, so no rush on this. (I thought maybe if I got this done quickly I could sneak this bug fix into 8.7, but that may be unrealistic at this point.)

@mathomp4
Copy link
Contributor

mathomp4 commented Sep 6, 2024

Just a note. We had another user on Linux 6.8+ (@gmao-ckung) hit this, so it might become more prevalent!

Thanks for looking at this!

@oehmke
Copy link
Contributor Author

oehmke commented Sep 6, 2024 via email

billsacks added a commit that referenced this issue Sep 19, 2024
Fix sprintf issues

Fixes uses of sprintf where the output string is also used in the input, which can result in garbled time strings.

Also changes these sprintf calls to snprintf to avoid possible buffer overflows, and checks these calls to ensure that the output variable is large enough to hold the relevant string, returning an error code if not.

Resolves #284
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants