strncpy() has a notoriously error-prone semantics which makes GCC
complain about it a lot (and quite often completely completely falsely
at that). Instead of pleasing GCC all the time (-Wno-stringop-truncation
is unfortunately only supported by GCC, so it's a bit too messy to just
enable it in Makefile), add libbpf-internal libbpf_strlcpy() helper
which follows what FreeBSD's strlcpy() does and what most people would
expect from strncpy(): copies up to N-1 first bytes from source string
into destination string and ensures zero-termination afterwards.
Replace all the relevant uses of strncpy/strncat/memcpy in libbpf with
libbpf_strlcpy().
This also fixes the issue reported by Emmanuel Deloget in xsk.c where
memcpy() could access source string beyond its end.
Fixes: 2f6324a3937f8 (libbpf: Support shared umems between queues and devices)
Reported-by: Emmanuel Deloget <emmanuel.deloget@eho.link>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/20211211004043.2374068-1-andrii@kernel.org
 
        attr.map_type = map_type;
        if (map_name)
-               strncat(attr.map_name, map_name, sizeof(attr.map_name) - 1);
+               libbpf_strlcpy(attr.map_name, map_name, sizeof(attr.map_name));
        attr.key_size = key_size;
        attr.value_size = value_size;
        attr.max_entries = max_entries;
        attr.kern_version = OPTS_GET(opts, kern_version, 0);
 
        if (prog_name)
-               strncat(attr.prog_name, prog_name, sizeof(attr.prog_name) - 1);
+               libbpf_strlcpy(attr.prog_name, prog_name, sizeof(attr.prog_name));
        attr.license = ptr_to_u64(license);
 
        if (insn_cnt > UINT_MAX)
 
        if (!opts->indent_str)
                d->typed_dump->indent_str[0] = '\t';
        else
-               strncat(d->typed_dump->indent_str, opts->indent_str,
-                       sizeof(d->typed_dump->indent_str) - 1);
+               libbpf_strlcpy(d->typed_dump->indent_str, opts->indent_str,
+                              sizeof(d->typed_dump->indent_str));
 
        d->typed_dump->compact = OPTS_GET(opts, compact, false);
        d->typed_dump->skip_names = OPTS_GET(opts, skip_names, false);
 
        attr.map_flags = map_attr->map_flags;
        attr.map_extra = map_attr->map_extra;
        if (map_name)
-               memcpy(attr.map_name, map_name,
-                      min((unsigned)strlen(map_name), BPF_OBJ_NAME_LEN - 1));
+               libbpf_strlcpy(attr.map_name, map_name, sizeof(attr.map_name));
        attr.numa_node = map_attr->numa_node;
        attr.map_ifindex = map_attr->map_ifindex;
        attr.max_entries = max_entries;
        core_relos = add_data(gen, gen->core_relos,
                             attr.core_relo_cnt * attr.core_relo_rec_size);
 
-       memcpy(attr.prog_name, prog_name,
-              min((unsigned)strlen(prog_name), BPF_OBJ_NAME_LEN - 1));
+       libbpf_strlcpy(attr.prog_name, prog_name, sizeof(attr.prog_name));
        prog_load_attr = add_data(gen, &attr, attr_size);
 
        /* populate union bpf_attr with a pointer to license */
 
 
        strcpy(obj->path, path);
        if (obj_name) {
-               strncpy(obj->name, obj_name, sizeof(obj->name) - 1);
-               obj->name[sizeof(obj->name) - 1] = 0;
+               libbpf_strlcpy(obj->name, obj_name, sizeof(obj->name));
        } else {
                /* Using basename() GNU version which doesn't modify arg. */
-               strncpy(obj->name, basename((void *)path),
-                       sizeof(obj->name) - 1);
+               libbpf_strlcpy(obj->name, basename((void *)path), sizeof(obj->name));
                end = strchr(obj->name, '.');
                if (end)
                        *end = 0;
 static int
 bpf_object__init_license(struct bpf_object *obj, void *data, size_t size)
 {
-       memcpy(obj->license, data, min(size, sizeof(obj->license) - 1));
+       libbpf_strlcpy(obj->license, data, sizeof(obj->license));
        pr_debug("license of %s is %s\n", obj->path, obj->license);
        return 0;
 }
 
        return realloc(ptr, total);
 }
 
+/* Copy up to sz - 1 bytes from zero-terminated src string and ensure that dst
+ * is zero-terminated string no matter what (unless sz == 0, in which case
+ * it's a no-op). It's conceptually close to FreeBSD's strlcpy(), but differs
+ * in what is returned. Given this is internal helper, it's trivial to extend
+ * this, when necessary. Use this instead of strncpy inside libbpf source code.
+ */
+static inline void libbpf_strlcpy(char *dst, const char *src, size_t sz)
+{
+       size_t i;
+
+       if (sz == 0)
+               return;
+
+       sz--;
+       for (i = 0; i < sz && src[i]; i++)
+               dst[i] = src[i];
+       dst[i] = '\0';
+}
+
 struct btf;
 struct btf_type;
 
 
                return -errno;
 
        ifr.ifr_data = (void *)&channels;
-       memcpy(ifr.ifr_name, ctx->ifname, IFNAMSIZ - 1);
-       ifr.ifr_name[IFNAMSIZ - 1] = '\0';
+       libbpf_strlcpy(ifr.ifr_name, ctx->ifname, IFNAMSIZ);
        err = ioctl(fd, SIOCETHTOOL, &ifr);
        if (err && errno != EOPNOTSUPP) {
                ret = -errno;
        }
 
        ctx->ifindex = ifindex;
-       memcpy(ctx->ifname, ifname, IFNAMSIZ -1);
-       ctx->ifname[IFNAMSIZ - 1] = 0;
+       libbpf_strlcpy(ctx->ifname, ifname, IFNAMSIZ);
 
        xsk->ctx = ctx;
        xsk->ctx->has_bpf_link = xsk_probe_bpf_link();
        ctx->refcount = 1;
        ctx->umem = umem;
        ctx->queue_id = queue_id;
-       memcpy(ctx->ifname, ifname, IFNAMSIZ - 1);
-       ctx->ifname[IFNAMSIZ - 1] = '\0';
+       libbpf_strlcpy(ctx->ifname, ifname, IFNAMSIZ);
 
        ctx->fill = fill;
        ctx->comp = comp;