Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-03 Thread Kees Cook
On Sat, Oct 03, 2020 at 03:55:51PM +0200, Greg Kroah-Hartman wrote:
> On Fri, Oct 02, 2020 at 03:30:30PM -0700, Kees Cook wrote:
> > On Wed, Sep 30, 2020 at 09:22:19PM -0700, Joe Perches wrote:
> > > On Wed, 2020-09-30 at 21:17 -0700, Kees Cook wrote:
> > > > On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > > > > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > > > > sysfs files, questions below:
> > > > 
> > > > I'm a fan. I think the use of sprintf() in sysfs might have been one of
> > > > my earliest complaints about unsafe code patterns in the kernel. ;)
> > > []
> > > > > > +   if (WARN(!buf || offset_in_page(buf),
> > > > > > +"invalid sysfs_emit: buf:%p\n", buf))
> > > 
> > > The dump_stack() is also going to emit pointers
> > > so I don't see how it does anything but help
> > > show where the buffer was.  It is hashed...
> > 
> > dump_stack() is going to report symbols and register contents.
> > 
> > I was just pointing out that %p has no value here[1]. The interesting
> > states are: "was it NULL?" "how offset was it?". Its actual content
> > won't matter.
> 
> Ok, suggestions for a better error message are always welcome :)

... I did. :P

https://lore.kernel.org/lkml/202009302108.18B05CA38@keescook/

But it doesn't need to hold up the series; %p is hashed, so I don't
care. It's just that the mandate from Linus was to not add new %p uses.
:P

-- 
Kees Cook


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-03 Thread Joe Perches
On Sat, 2020-10-03 at 15:55 +0200, Greg Kroah-Hartman wrote:
> On Fri, Oct 02, 2020 at 03:30:30PM -0700, Kees Cook wrote:
> > On Wed, Sep 30, 2020 at 09:22:19PM -0700, Joe Perches wrote:
> > > On Wed, 2020-09-30 at 21:17 -0700, Kees Cook wrote:
> > > > On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > > > > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > > > > sysfs files, questions below:
> > > > 
> > > > I'm a fan. I think the use of sprintf() in sysfs might have been one of
> > > > my earliest complaints about unsafe code patterns in the kernel. ;)
> > > []
> > > > > > +   if (WARN(!buf || offset_in_page(buf),
> > > > > > +"invalid sysfs_emit: buf:%p\n", buf))
> > > 
> > > The dump_stack() is also going to emit pointers
> > > so I don't see how it does anything but help
> > > show where the buffer was.  It is hashed...
> > 
> > dump_stack() is going to report symbols and register contents.
> > 
> > I was just pointing out that %p has no value here[1]. The interesting
> > states are: "was it NULL?" "how offset was it?". Its actual content
> > won't matter.
> 
> Ok, suggestions for a better error message are always welcome :)

For sysfs_emit, the offset_in_buf test also
effectively checks if buf not PAGE_SIZE so
it helps identify if it is being called from
a non _show function.

That's actually why %p can be somewhat valuable.




Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-03 Thread Greg Kroah-Hartman
On Fri, Oct 02, 2020 at 03:30:30PM -0700, Kees Cook wrote:
> On Wed, Sep 30, 2020 at 09:22:19PM -0700, Joe Perches wrote:
> > On Wed, 2020-09-30 at 21:17 -0700, Kees Cook wrote:
> > > On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > > > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > > > sysfs files, questions below:
> > > 
> > > I'm a fan. I think the use of sprintf() in sysfs might have been one of
> > > my earliest complaints about unsafe code patterns in the kernel. ;)
> > []
> > > > > + if (WARN(!buf || offset_in_page(buf),
> > > > > +  "invalid sysfs_emit: buf:%p\n", buf))
> > 
> > The dump_stack() is also going to emit pointers
> > so I don't see how it does anything but help
> > show where the buffer was.  It is hashed...
> 
> dump_stack() is going to report symbols and register contents.
> 
> I was just pointing out that %p has no value here[1]. The interesting
> states are: "was it NULL?" "how offset was it?". Its actual content
> won't matter.

Ok, suggestions for a better error message are always welcome :)

thanks,

greg k-h


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-02 Thread Kees Cook
On Wed, Sep 30, 2020 at 09:22:19PM -0700, Joe Perches wrote:
> On Wed, 2020-09-30 at 21:17 -0700, Kees Cook wrote:
> > On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > > sysfs files, questions below:
> > 
> > I'm a fan. I think the use of sprintf() in sysfs might have been one of
> > my earliest complaints about unsafe code patterns in the kernel. ;)
> []
> > > > +   if (WARN(!buf || offset_in_page(buf),
> > > > +"invalid sysfs_emit: buf:%p\n", buf))
> 
> The dump_stack() is also going to emit pointers
> so I don't see how it does anything but help
> show where the buffer was.  It is hashed...

dump_stack() is going to report symbols and register contents.

I was just pointing out that %p has no value here[1]. The interesting
states are: "was it NULL?" "how offset was it?". Its actual content
won't matter.

-Kees

[1] "New uses of %p should not be added to the kernel"

https://www.kernel.org/doc/html/latest/process/deprecated.html#p-format-specifier

-- 
Kees Cook


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-02 Thread Kees Cook
On Thu, Oct 01, 2020 at 10:50:29PM +0200, Greg Kroah-Hartman wrote:
> On Wed, Sep 30, 2020 at 09:17:03PM -0700, Kees Cook wrote:
> > On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > > sysfs files, questions below:
> > 
> > I'm a fan. I think the use of sprintf() in sysfs might have been one of
> > my earliest complaints about unsafe code patterns in the kernel. ;)
> 
> Ok, great.
> 
> > > > +/**
> > > > + * sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer.
> > > > + * @buf:   start of PAGE_SIZE buffer.
> > > > + * @fmt:   format
> > > > + * @...:   optional arguments to @format
> > > > + *
> > > > + *
> > > > + * Returns number of characters written to @buf.
> > > > + */
> > > > +int sysfs_emit(char *buf, const char *fmt, ...)
> > > > +{
> > > > +   va_list args;
> > > > +   int len;
> > > > +
> > > > +   if (WARN(!buf || offset_in_page(buf),
> > > > +"invalid sysfs_emit: buf:%p\n", buf))
> > 
> > I don't want the %p here, but otherwise, sure. I'd also make it a _ONCE
> > variant:
> > 
> > if (WARN_ONCE(!buf || offset_in_page(buf),
> >  "invalid sysfs_emit: offset_in_page(buf):%zd\n",
> >   buf ? offset_in_page(buf) : 0))
> 
> As Joe points out, _ONCE doesn't work because this happens from all
> sysfs files, not just one.

Sure, it's just a question if you want log spamming vs how reachable you
think something might be. I would expect this to be uncommon to
encounter, but very repeatable for whatever system DOES hit it, so doing
_ONCE means they see the report and don't get completely flooded with
it.

I'm fine either way.

-- 
Kees Cook


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-02 Thread Greg Kroah-Hartman
On Wed, Sep 30, 2020 at 06:58:53AM -0700, Joe Perches wrote:
> On Wed, 2020-09-30 at 13:57 +0200, Greg Kroah-Hartman wrote:
> > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > sysfs files, questions below:
> 
> https://lore.kernel.org/linux-pm/5d606519698ce4c8f1203a2b35797d8254c6050a.1600285923.git@perches.com/T/
> 
> > So I guess I'm asking for another developer to at least agree that this
> > feels like the right way forward here.  I don't want to start down this
> > path, only to roll them all back as it feels like pointless churn.
> 
> https://lore.kernel.org/lkml/c256eba42a564c01a8e470320475d...@acums.aculab.com/T/#mb40d265bc1dabb8bb64b0dfa29dd8eda44be056e
> 
> 
> 
> 

All now queued up, thanks!

greg k-h


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-10-01 Thread Greg Kroah-Hartman
On Wed, Sep 30, 2020 at 09:17:03PM -0700, Kees Cook wrote:
> On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > sysfs files, questions below:
> 
> I'm a fan. I think the use of sprintf() in sysfs might have been one of
> my earliest complaints about unsafe code patterns in the kernel. ;)

Ok, great.

> > > +/**
> > > + *   sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer.
> > > + *   @buf:   start of PAGE_SIZE buffer.
> > > + *   @fmt:   format
> > > + *   @...:   optional arguments to @format
> > > + *
> > > + *
> > > + * Returns number of characters written to @buf.
> > > + */
> > > +int sysfs_emit(char *buf, const char *fmt, ...)
> > > +{
> > > + va_list args;
> > > + int len;
> > > +
> > > + if (WARN(!buf || offset_in_page(buf),
> > > +  "invalid sysfs_emit: buf:%p\n", buf))
> 
> I don't want the %p here, but otherwise, sure. I'd also make it a _ONCE
> variant:
> 
>   if (WARN_ONCE(!buf || offset_in_page(buf),
>"invalid sysfs_emit: offset_in_page(buf):%zd\n",
> buf ? offset_in_page(buf) : 0))

As Joe points out, _ONCE doesn't work because this happens from all
sysfs files, not just one.

thanks,

greg k-h


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-09-30 Thread Joe Perches
On Wed, 2020-09-30 at 21:17 -0700, Kees Cook wrote:
> On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> > Kees, and Rafael, I don't know if you saw this proposal from Joe for
> > sysfs files, questions below:
> 
> I'm a fan. I think the use of sprintf() in sysfs might have been one of
> my earliest complaints about unsafe code patterns in the kernel. ;)
[]
> > > + if (WARN(!buf || offset_in_page(buf),
> > > +  "invalid sysfs_emit: buf:%p\n", buf))

The dump_stack() is also going to emit pointers
so I don't see how it does anything but help
show where the buffer was.  It is hashed...

> I don't want the %p here, but otherwise, sure. I'd also make it a _ONCE
> variant:
> 
>   if (WARN_ONCE(!buf || offset_in_page(buf),
>"invalid sysfs_emit: offset_in_page(buf):%zd\n",
> buf ? offset_in_page(buf) : 0))

I don't think that helps as multiple defects can easily
exist.  Log spam in this case isn't horrible either.




Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-09-30 Thread Kees Cook
On Wed, Sep 30, 2020 at 01:57:40PM +0200, Greg Kroah-Hartman wrote:
> Kees, and Rafael, I don't know if you saw this proposal from Joe for
> sysfs files, questions below:

I'm a fan. I think the use of sprintf() in sysfs might have been one of
my earliest complaints about unsafe code patterns in the kernel. ;)

> > +/**
> > + * sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer.
> > + * @buf:   start of PAGE_SIZE buffer.
> > + * @fmt:   format
> > + * @...:   optional arguments to @format
> > + *
> > + *
> > + * Returns number of characters written to @buf.
> > + */
> > +int sysfs_emit(char *buf, const char *fmt, ...)
> > +{
> > +   va_list args;
> > +   int len;
> > +
> > +   if (WARN(!buf || offset_in_page(buf),
> > +"invalid sysfs_emit: buf:%p\n", buf))

I don't want the %p here, but otherwise, sure. I'd also make it a _ONCE
variant:

if (WARN_ONCE(!buf || offset_in_page(buf),
 "invalid sysfs_emit: offset_in_page(buf):%zd\n",
  buf ? offset_in_page(buf) : 0))

> > +   return 0;
> > +
> > +   va_start(args, fmt);
> > +   len = vscnprintf(buf, PAGE_SIZE, fmt, args);
> > +   va_end(args);
> > +
> > +   return len;
> > +}
> > +EXPORT_SYMBOL_GPL(sysfs_emit);
> > +
> > +/**
> > + * sysfs_emit_at - scnprintf equivalent, aware of PAGE_SIZE buffer.
> > + * @buf:   start of PAGE_SIZE buffer.
> > + * @at:offset in @buf to start write in bytes
> > + * @at must be >= 0 && < PAGE_SIZE
> > + * @fmt:   format
> > + * @...:   optional arguments to @fmt
> > + *
> > + *
> > + * Returns number of characters written starting at &@buf[@at].
> > + */
> > +int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
> > +{
> > +   va_list args;
> > +   int len;
> > +
> > +   if (WARN(!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE,
> > +"invalid sysfs_emit_at: buf:%p at:%d\n", buf, at))

Same:

if (WARN_ONCE(!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE,
 "invalid sysfs_emit_at: offset_in_page(buf):%zd at:%d\n",
  buf ? offset_in_page(buf) : 0, at))

> > +   return 0;
> > +
> > +   va_start(args, fmt);
> > +   len = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
> > +   va_end(args);
> > +
> > +   return len;
> > +}
> > +EXPORT_SYMBOL_GPL(sysfs_emit_at);
> 
> These feel sane, but I'm loath to have a ton of churn for no good
> reason.

I think the churn is worth it if we remove "seemingly wrong" code
patterns from the kernel. It's especially useful, IMO, for when there
are future mutations/refactorings and we don't end up with a bare
sprintf somewhere else.

> If we make all sysfs show/store functions use these calls instead of
> sprintf(), it "feels" like that might address the objections that people
> have had in the past where they are nervous about "bare" sprintf()
> calls, right?

I would think so. This is the kind of thing we did for %n in seq_file:
remove potential foot-gun API in favor of subsystem-specific safe API.

> It also might make things easier to audit where we can see much easier
> where sysfs files are doing "foolish" things by calling sysfs_emit_at()
> a bunch of times they shouldn't be, and maybe automate the documentation
> of sysfs files in a better way.

Indeed!

> So I guess I'm asking for another developer to at least agree that this
> feels like the right way forward here.  I don't want to start down this
> path, only to roll them all back as it feels like pointless churn.

With the changes above, I'd Ack it. :)

-- 
Kees Cook


Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-09-30 Thread Joe Perches
On Wed, 2020-09-30 at 13:57 +0200, Greg Kroah-Hartman wrote:
> Kees, and Rafael, I don't know if you saw this proposal from Joe for
> sysfs files, questions below:

https://lore.kernel.org/linux-pm/5d606519698ce4c8f1203a2b35797d8254c6050a.1600285923.git@perches.com/T/

> So I guess I'm asking for another developer to at least agree that this
> feels like the right way forward here.  I don't want to start down this
> path, only to roll them all back as it feels like pointless churn.

https://lore.kernel.org/lkml/c256eba42a564c01a8e470320475d...@acums.aculab.com/T/#mb40d265bc1dabb8bb64b0dfa29dd8eda44be056e






Re: [PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-09-30 Thread Greg Kroah-Hartman
Kees, and Rafael, I don't know if you saw this proposal from Joe for
sysfs files, questions below:

On Wed, Sep 16, 2020 at 01:40:38PM -0700, Joe Perches wrote:
> Output defects can exist in sysfs content using sprintf and snprintf.
> 
> sprintf does not know the PAGE_SIZE maximum of the temporary buffer
> used for outputting sysfs content and it's possible to overrun the
> PAGE_SIZE buffer length.
> 
> Add a generic sysfs_emit function that knows that the size of the
> temporary buffer and ensures that no overrun is done.
> 
> Add a generic sysfs_emit_at function that can be used in multiple
> call situations that also ensures that no overrun is done.
> 
> Validate the output buffer argument to be page aligned.
> Validate the offset len argument to be within the PAGE_SIZE buf.
> 
> Signed-off-by: Joe Perches 
> ---
>  Documentation/filesystems/sysfs.rst |  8 ++---
>  fs/sysfs/file.c | 55 +
>  include/linux/sysfs.h   | 15 
>  3 files changed, 73 insertions(+), 5 deletions(-)
> 
> diff --git a/Documentation/filesystems/sysfs.rst 
> b/Documentation/filesystems/sysfs.rst
> index 5a3209a4cebf..004d490179f3 100644
> --- a/Documentation/filesystems/sysfs.rst
> +++ b/Documentation/filesystems/sysfs.rst
> @@ -241,12 +241,10 @@ Other notes:
>is 4096.
>  
>  - show() methods should return the number of bytes printed into the
> -  buffer. This is the return value of scnprintf().
> +  buffer.
>  
> -- show() must not use snprintf() when formatting the value to be
> -  returned to user space. If you can guarantee that an overflow
> -  will never happen you can use sprintf() otherwise you must use
> -  scnprintf().
> +- show() should only use sysfs_emit() or sysfs_emit_at() when formatting
> +  the value to be returned to user space.
>  
>  - store() should return the number of bytes used from the buffer. If the
>entire buffer has been used, just return the count argument.
> diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
> index eb6897ab78e7..96d0da65e088 100644
> --- a/fs/sysfs/file.c
> +++ b/fs/sysfs/file.c
> @@ -15,6 +15,7 @@
>  #include 
>  #include 
>  #include 
> +#include 
>  
>  #include "sysfs.h"
>  
> @@ -707,3 +708,57 @@ int sysfs_change_owner(struct kobject *kobj, kuid_t 
> kuid, kgid_t kgid)
>   return 0;
>  }
>  EXPORT_SYMBOL_GPL(sysfs_change_owner);
> +
> +/**
> + *   sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer.
> + *   @buf:   start of PAGE_SIZE buffer.
> + *   @fmt:   format
> + *   @...:   optional arguments to @format
> + *
> + *
> + * Returns number of characters written to @buf.
> + */
> +int sysfs_emit(char *buf, const char *fmt, ...)
> +{
> + va_list args;
> + int len;
> +
> + if (WARN(!buf || offset_in_page(buf),
> +  "invalid sysfs_emit: buf:%p\n", buf))
> + return 0;
> +
> + va_start(args, fmt);
> + len = vscnprintf(buf, PAGE_SIZE, fmt, args);
> + va_end(args);
> +
> + return len;
> +}
> +EXPORT_SYMBOL_GPL(sysfs_emit);
> +
> +/**
> + *   sysfs_emit_at - scnprintf equivalent, aware of PAGE_SIZE buffer.
> + *   @buf:   start of PAGE_SIZE buffer.
> + *   @at:offset in @buf to start write in bytes
> + *   @at must be >= 0 && < PAGE_SIZE
> + *   @fmt:   format
> + *   @...:   optional arguments to @fmt
> + *
> + *
> + * Returns number of characters written starting at &@buf[@at].
> + */
> +int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
> +{
> + va_list args;
> + int len;
> +
> + if (WARN(!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE,
> +  "invalid sysfs_emit_at: buf:%p at:%d\n", buf, at))
> + return 0;
> +
> + va_start(args, fmt);
> + len = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
> + va_end(args);
> +
> + return len;
> +}
> +EXPORT_SYMBOL_GPL(sysfs_emit_at);

These feel sane, but I'm loath to have a ton of churn for no good
reason.

If we make all sysfs show/store functions use these calls instead of
sprintf(), it "feels" like that might address the objections that people
have had in the past where they are nervous about "bare" sprintf()
calls, right?

It also might make things easier to audit where we can see much easier
where sysfs files are doing "foolish" things by calling sysfs_emit_at()
a bunch of times they shouldn't be, and maybe automate the documentation
of sysfs files in a better way.

So I guess I'm asking for another developer to at least agree that this
feels like the right way forward here.  I don't want to start down this
path, only to roll them all back as it feels like pointless churn.

thanks,

greg k-h


[PATCH V3 1/8] sysfs: Add sysfs_emit and sysfs_emit_at to format sysfs output

2020-09-16 Thread Joe Perches
Output defects can exist in sysfs content using sprintf and snprintf.

sprintf does not know the PAGE_SIZE maximum of the temporary buffer
used for outputting sysfs content and it's possible to overrun the
PAGE_SIZE buffer length.

Add a generic sysfs_emit function that knows that the size of the
temporary buffer and ensures that no overrun is done.

Add a generic sysfs_emit_at function that can be used in multiple
call situations that also ensures that no overrun is done.

Validate the output buffer argument to be page aligned.
Validate the offset len argument to be within the PAGE_SIZE buf.

Signed-off-by: Joe Perches 
---
 Documentation/filesystems/sysfs.rst |  8 ++---
 fs/sysfs/file.c | 55 +
 include/linux/sysfs.h   | 15 
 3 files changed, 73 insertions(+), 5 deletions(-)

diff --git a/Documentation/filesystems/sysfs.rst 
b/Documentation/filesystems/sysfs.rst
index 5a3209a4cebf..004d490179f3 100644
--- a/Documentation/filesystems/sysfs.rst
+++ b/Documentation/filesystems/sysfs.rst
@@ -241,12 +241,10 @@ Other notes:
   is 4096.
 
 - show() methods should return the number of bytes printed into the
-  buffer. This is the return value of scnprintf().
+  buffer.
 
-- show() must not use snprintf() when formatting the value to be
-  returned to user space. If you can guarantee that an overflow
-  will never happen you can use sprintf() otherwise you must use
-  scnprintf().
+- show() should only use sysfs_emit() or sysfs_emit_at() when formatting
+  the value to be returned to user space.
 
 - store() should return the number of bytes used from the buffer. If the
   entire buffer has been used, just return the count argument.
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index eb6897ab78e7..96d0da65e088 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -15,6 +15,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "sysfs.h"
 
@@ -707,3 +708,57 @@ int sysfs_change_owner(struct kobject *kobj, kuid_t kuid, 
kgid_t kgid)
return 0;
 }
 EXPORT_SYMBOL_GPL(sysfs_change_owner);
+
+/**
+ * sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer.
+ * @buf:   start of PAGE_SIZE buffer.
+ * @fmt:   format
+ * @...:   optional arguments to @format
+ *
+ *
+ * Returns number of characters written to @buf.
+ */
+int sysfs_emit(char *buf, const char *fmt, ...)
+{
+   va_list args;
+   int len;
+
+   if (WARN(!buf || offset_in_page(buf),
+"invalid sysfs_emit: buf:%p\n", buf))
+   return 0;
+
+   va_start(args, fmt);
+   len = vscnprintf(buf, PAGE_SIZE, fmt, args);
+   va_end(args);
+
+   return len;
+}
+EXPORT_SYMBOL_GPL(sysfs_emit);
+
+/**
+ * sysfs_emit_at - scnprintf equivalent, aware of PAGE_SIZE buffer.
+ * @buf:   start of PAGE_SIZE buffer.
+ * @at:offset in @buf to start write in bytes
+ * @at must be >= 0 && < PAGE_SIZE
+ * @fmt:   format
+ * @...:   optional arguments to @fmt
+ *
+ *
+ * Returns number of characters written starting at &@buf[@at].
+ */
+int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
+{
+   va_list args;
+   int len;
+
+   if (WARN(!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE,
+"invalid sysfs_emit_at: buf:%p at:%d\n", buf, at))
+   return 0;
+
+   va_start(args, fmt);
+   len = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
+   va_end(args);
+
+   return len;
+}
+EXPORT_SYMBOL_GPL(sysfs_emit_at);
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index 34e84122f635..2caa34c1ca1a 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -329,6 +329,10 @@ int sysfs_groups_change_owner(struct kobject *kobj,
 int sysfs_group_change_owner(struct kobject *kobj,
 const struct attribute_group *groups, kuid_t kuid,
 kgid_t kgid);
+__printf(2, 3)
+int sysfs_emit(char *buf, const char *fmt, ...);
+__printf(3, 4)
+int sysfs_emit_at(char *buf, int at, const char *fmt, ...);
 
 #else /* CONFIG_SYSFS */
 
@@ -576,6 +580,17 @@ static inline int sysfs_group_change_owner(struct kobject 
*kobj,
return 0;
 }
 
+__printf(2, 3)
+static inline int sysfs_emit(char *buf, const char *fmt, ...)
+{
+   return 0;
+}
+
+__printf(3, 4)
+static inline int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
+{
+   return 0;
+}
 #endif /* CONFIG_SYSFS */
 
 static inline int __must_check sysfs_create_file(struct kobject *kobj,
-- 
2.26.0